memory detection
[bootcensus] / src / boot / boot2.s
1 # pcboot - bootable PC demo/game kernel
2 # Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
3
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY, without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17 # this is the second-stage boot loader
18         .code16
19         .section .boot2,"ax"
20
21         .set main_load_addr, 0x100000
22
23         # make sure any BIOS call didn't re-enable interrupts
24         cli
25
26         # enter unreal mode
27         call unreal
28
29         movb $10, %al
30         call ser_putchar
31
32         call clearscr
33
34         # enable A20 address line
35         call enable_a20
36
37         # detect available memory
38         call detect_memory
39
40         # load the whole program into memory starting at 1MB
41         call load_main
42
43         #mov $0x13, %ax
44         #int $0x10
45
46         # load initial GDT
47         lgdt (gdt_lim)
48         # load initial IDT
49         lidt (idt_lim)
50
51         # enter protected mode for the first time
52         mov %cr0, %eax
53         or $1, %eax
54         mov %eax, %cr0
55         # inter-segment jump to set cs selector to segment 1
56         ljmp $0x8,$0f
57
58         .code32
59         # set all data selectors to segment 2
60 0:      mov $0x10, %ax
61         mov %ax, %ds
62         mov %ax, %ss
63         mov %ax, %es
64         mov %ax, %gs
65         mov %ax, %fs
66
67         jmp main_load_addr
68
69         cli
70 0:      hlt
71         jmp 0b
72
73
74
75         .align 4
76 gdt_lim: .word 23
77 gdt_base:.long gdt
78
79         .align 4
80 idt_lim: .word 111
81 idt_base:.long idt
82
83
84         .align 8
85 gdt:    # 0: null segment
86         .long 0
87         .long 0
88         # 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
89         .long 0x0000ffff
90         .long 0x00cf9a00
91         # 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
92         .long 0x0000ffff
93         .long 0x00cf9200
94
95
96         .align 8
97 idt:    .space 104
98         # trap gate 13: general protection fault
99         .short prot_fault
100         .short 0x8
101         # type: trap, present, default
102         .short 0x8f00
103         .short 0
104
105 gpf_msg: .asciz "GP fault "
106
107 prot_fault:
108         mov (%esp), %eax
109         shr $3, %eax
110         call print_num
111         mov $64, %al
112         call putchar
113         mov 4(%esp), %eax
114         call print_num
115         mov $10, %al
116         call putchar
117         hlt
118
119         .code16
120 unreal:
121         # use the same GDT above, will use data segment: 2
122         lgdt (gdt_lim)
123
124         mov %cr0, %eax
125         or $1, %ax
126         mov %eax, %cr0
127         jmp 0f
128
129 0:      mov $0x10, %ax
130         mov %ax, %ds
131         mov %ax, %es
132         mov %ax, %fs
133         mov %ax, %gs
134         mov %ax, %ss
135
136         mov %cr0, %eax
137         and $0xfffe, %ax
138         mov %eax, %cr0
139
140         xor %ax, %ax
141         mov %ax, %ds
142         mov %ax, %es
143         mov %ax, %fs
144         mov %ax, %gs
145         mov %ax, %ss
146         ret
147
148 mainsz_msg: .asciz "Main program size: "
149 mainsz_msg2: .asciz " ("
150 mainsz_msg3: .asciz " sectors)\n"
151
152 first_sect: .long 0
153 sect_left: .long 0
154 cur_track: .long 0
155 trk_sect: .long 0
156 dest_ptr: .long 0
157
158 load_main:
159         movl $main_load_addr, dest_ptr
160
161         # calculate first sector
162         mov $_boot2_size, %eax
163         add $511, %eax
164         shr $9, %eax
165         # add 1 to account for the boot sector
166         inc %eax
167         mov %eax, first_sect
168
169         # calculate the first track (first_sect / sect_per_track)
170         movzxw sect_per_track, %ecx
171         xor %edx, %edx
172         div %ecx
173         mov %eax, cur_track
174         # remainder is sector within track
175         mov %edx, trk_sect
176
177         mov $mainsz_msg, %esi
178         call putstr
179         mov $_main_size, %eax
180         mov %eax, %ecx
181         call print_num
182
183         mov $mainsz_msg2, %esi
184         call putstr
185
186         # calculate sector count
187         add $511, %eax
188         shr $9, %eax
189         mov %eax, sect_left
190
191         call print_num
192         mov $mainsz_msg3, %esi
193         call putstr
194
195         # read a whole track into the buffer (or partial first track)
196 ldloop:
197         movzxw sect_per_track, %ecx
198         sub trk_sect, %ecx
199         push %ecx
200         call read_track
201
202         # debug: print the first 32bits of the track
203         #mov buffer, %eax
204         #call print_num
205         #mov $10, %al
206         #call putchar
207
208         # copy to high memory
209         mov $buffer, %esi
210         mov dest_ptr, %edi
211         mov (%esp), %ecx
212         shl $9, %ecx
213         add %ecx, dest_ptr
214         shr $2, %ecx
215         addr32 rep movsl
216
217         incl cur_track
218         # other than the first track which might be partial, all the rest start from 0
219         movl $0, trk_sect
220
221         pop %ecx
222         sub %ecx, sect_left
223         ja ldloop
224
225         # the BIOS might have enabled interrupts
226         cli
227
228         # just in case we were loaded from floppy, turn all floppy motors off
229         mov $0x3f2, %dx
230         in %dx, %al
231         and $0xf0, %al
232         out %al, %dx
233
234         mov $10, %ax
235         call putchar
236
237         ret
238
239 rdtrk_msg: .asciz "Reading track: "
240 rdcyl_msg: .asciz " - cyl: "
241 rdhead_msg: .asciz " head: "
242 rdsect_msg: .asciz " start sect: "
243 rdlast_msg: .asciz " ... "
244 rdok_msg: .asciz "OK\n"
245 rdfail_msg: .asciz "failed\n"
246
247 read_retries: .short 0
248
249         .set drive_number, 0x7bec
250 read_track:
251         # set es to the start of the destination buffer to allow reading in
252         # full 64k chunks if necessary
253         mov $buffer, %bx
254         shr $4, %bx
255         mov %bx, %es
256         xor %ebx, %ebx
257
258         movw $3, read_retries
259
260 read_try:
261         # print track
262         mov $rdtrk_msg, %esi
263         call putstr
264         mov cur_track, %eax
265         call print_num
266         mov $rdcyl_msg, %esi
267         call putstr
268
269         # calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
270         mov cur_track, %eax
271         movzxw num_heads, %ecx
272         xor %edx, %edx
273         div %ecx
274
275         # print cylinder
276         push %eax
277         call print_num
278         # print head
279         mov $rdhead_msg, %esi
280         call putstr
281         movzx %dx, %eax
282         call print_num
283         pop %eax
284
285         # head in dh
286         mov %dl, %dh
287
288         # cylinder low byte at ch and high bits at cl[7, 6]
289         mov %al, %ch
290         mov %ah, %cl
291         and $3, %cl
292         ror $2, %cl
293
294         # print start sector
295         mov $rdsect_msg, %esi
296         call putstr
297         mov trk_sect, %eax
298         call print_num
299         mov $rdlast_msg, %esi
300         call putstr
301
302         # start sector (1-based) in cl[0, 5]
303         mov trk_sect, %al
304         inc %al
305         and $0x3f, %al
306         or %al, %cl
307
308         # number of sectors in al
309         mov 2(%esp), %ax
310         # call number (2) in ah
311         mov $2, %ah
312         # drive number in dl
313         movb drive_number, %dl
314         int $0x13
315         jnc read_ok
316
317         # abort after 3 attempts
318         decw read_retries
319         jz read_fail
320
321         # error, reset controller and retry
322         xor %ah, %ah
323         int $0x13
324         jmp read_try
325
326 read_fail:
327         mov $rdfail_msg, %esi
328         call putstr
329         jmp abort_read
330
331 read_ok:
332         mov $rdok_msg, %esi
333         call putstr
334
335         # reset es to 0 before returning
336         xor %ax, %ax
337         mov %ax, %es
338         ret
339
340 str_read_error: .asciz "Read error while reading track: "
341
342 abort_read:
343         mov $str_read_error, %esi
344         call putstr
345         mov cur_track, %eax
346         call print_num
347         mov $10, %al
348         call putchar
349
350         cli
351 0:      hlt
352         jmp 0b
353
354
355         # better print routines, since we're not constrainted by the 512b of
356         # the boot sector.
357         .global cursor_x
358         .global cursor_y
359 cursor_x: .long 0
360 cursor_y: .long 0
361
362 putchar:
363         pushal
364         call ser_putchar
365
366         cmp $10, %al
367         jnz 0f
368         call video_newline
369         jmp 1f
370
371 0:      push %eax
372         mov cursor_y, %eax
373         mov $80, %ecx
374         mul %ecx
375         add cursor_x, %eax
376         mov %eax, %ebx
377         pop %eax
378
379         mov $0xb8000, %edx
380
381         # this looks retarded. in nasm: [ebx * 2 + edx]
382         mov %al, (%edx, %ebx, 2)
383         movb $7, 1(%edx, %ebx, 2)
384         incl cursor_x
385         cmpl $80, cursor_x
386         jnz 1f
387         call video_newline
388
389 1:      popal
390         ret
391         
392         # expects string pointer in esi
393 putstr:
394         mov (%esi), %al
395         cmp $0, %al
396         jz 0f
397         call putchar
398         inc %esi
399         jmp putstr
400 0:      ret
401
402         # expects number in eax
403 print_num:
404         # save registers
405         pushal
406
407         mov $numbuf + 16, %esi
408         movb $0, (%esi)
409         mov $10, %ebx
410 convloop:
411         xor %edx, %edx
412         div %ebx
413         add $48, %dl
414         dec %esi
415         mov %dl, (%esi)
416         cmp $0, %eax
417         jnz convloop
418
419         call putstr
420
421         # restore regs
422         popal
423         ret
424
425
426 video_newline:
427         movl $0, cursor_x
428         incl cursor_y
429         cmpl $25, cursor_y
430         jnz 0f
431         call scrollup
432         decl cursor_y
433 0:      ret
434
435 scrollup:
436         pushal
437         # move 80 * 24 lines from b80a0 -> b8000
438         mov $0xb8000, %edi
439         mov $0xb80a0, %esi
440         mov $960, %ecx
441         addr32 rep movsl
442         # clear last line (b8f00)
443         mov $0xb8f00, %edi
444         xor %eax, %eax
445         mov $40, %ecx
446         addr32 rep stosl
447         popal
448         ret
449
450 clearscr:
451         mov $0xb8000, %edi
452         xor %eax, %eax
453         mov $1000, %ecx
454         addr32 rep stosl
455         ret
456
457         .set UART_DATA, 0x3f8
458         .set UART_LSTAT, 0x3fd
459         .set LST_TREG_EMPTY, 0x20
460
461 ser_putchar:
462         push %dx
463
464         cmp $10, %al
465         jnz 0f
466         push %ax
467         mov $13, %al
468         call ser_putchar
469         pop %ax
470
471 0:      mov %al, %ah
472         # wait until the transmit register is empty
473         mov $UART_LSTAT, %dx
474 wait:   in %dx, %al
475         and $LST_TREG_EMPTY, %al
476         jz wait
477         mov $UART_DATA, %dx
478         mov %ah, %al
479         out %al, %dx
480
481         pop %dx
482         ret
483
484
485
486 ena20_msg: .asciz "A20 line enabled\n"
487
488 enable_a20:
489         call test_a20
490         jnc a20done
491         call enable_a20_kbd
492         call test_a20
493         jnc a20done
494         call enable_a20_fast
495         call test_a20
496         jnc a20done
497         # keep trying ... we can't do anything useful without A20 anyway
498         jmp enable_a20
499 a20done:
500         mov $ena20_msg, %esi
501         call putstr
502         ret
503
504         # CF = 1 if A20 test fails (not enabled)
505 test_a20:
506         mov $0x07c000, %ebx
507         mov $0x17c000, %edx
508         movl $0xbaadf00d, (%ebx)
509         movl $0xaabbcc42, (%edx)
510         subl $0xbaadf00d, (%ebx)
511         ret
512
513         # enable A20 line through port 0x92 (fast A20)
514 enable_a20_fast:
515         mov $ena20_fast_msg, %esi
516         call putstr
517
518         in $0x92, %al
519         or $2, %al
520         out %al, $0x92
521         ret
522
523 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
524
525
526         # enable A20 line through the keyboard controller
527         .set KBC_DATA_PORT, 0x60
528         .set KBC_CMD_PORT, 0x64
529         .set KBC_STATUS_PORT, 0x64
530         .set KBC_CMD_RD_OUTPORT, 0xd0
531         .set KBC_CMD_WR_OUTPORT, 0xd1
532
533         .set KBC_STAT_OUT_RDY, 0x01
534         .set KBC_STAT_IN_FULL, 0x02
535
536 enable_a20_kbd:
537         mov $ena20_kbd_msg, %esi
538         call putstr
539
540         call kbc_wait_write
541         mov $KBC_CMD_WR_OUTPORT, %al
542         out %al, $KBC_CMD_PORT
543         call kbc_wait_write
544         mov $0xdf, %al
545         out %al, $KBC_DATA_PORT
546         ret
547
548 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
549
550         # wait until the keyboard controller is ready to accept another byte
551 kbc_wait_write:
552         in $KBC_STATUS_PORT, %al
553         and $KBC_STAT_IN_FULL, %al
554         jnz kbc_wait_write
555         ret
556
557 numbuf: .space 16
558
559
560 detect_memory:
561         mov $memdet_e820_msg, %esi
562         call putstr
563         call detect_mem_e820
564         jnc memdet_done
565         mov $rdfail_msg, %esi
566         call putstr
567
568         mov $memdet_e801_msg, %esi
569         call putstr
570         call detect_mem_e801
571         jnc memdet_done
572         mov $rdfail_msg, %esi
573         call putstr
574
575         mov $memdet_88_msg, %esi
576         call putstr
577         call detect_mem_88
578         jnc memdet_done
579         mov $rdfail_msg, %esi
580         call putstr
581
582         # just panic...
583         mov $memdet_fail_msg, %esi
584         call putstr
585 0:      hlt
586         jmp 0b
587
588 memdet_done:
589         mov $rdok_msg, %esi
590         call putstr
591         ret
592
593 memdet_fail_msg: .ascii "Failed to detect available memory!\n"
594                  .ascii "Please file a bug report: https://github.com/jtsiomb/pcboot/issues\n"
595                  .asciz " or contact me through email: nuclear@member.fsf.org\n"
596 memdet_e820_msg: .asciz "Detecting RAM (BIOS 15h/0xe820)... "
597 memdet_e801_msg: .asciz "Detecting RAM (BIOS 15h/0xe801)... " 
598 memdet_88_msg:   .asciz "Detecting RAM (BIOS 15h/0x88, max 64mb)... "
599
600         # detect extended memory using BIOS call 15h/e820
601 detect_mem_e820:
602         movl $0, boot_mem_map_size
603
604         mov $buffer, %edi
605         xor %ebx, %ebx
606         mov $0x534d4150, %edx
607
608 e820_looptop:
609         mov $0xe820, %eax
610         mov $24, %ecx
611         int $0x15
612         jc e820_fail
613         cmp $0x534d4150, %eax
614         jnz e820_fail
615
616         mov buffer, %eax
617         mov $boot_mem_map, %esi
618         mov boot_mem_map_size, %ebp
619         # again, that's [ebp * 8 + esi]
620         mov %eax, (%esi,%ebp,8)
621
622         # only care for type 1 (usable ram), otherwise ignore
623         cmpl $1, 16(%edi)
624         jnz e820_skip
625
626         # skip areas with 0 size (also clamp size to 4gb)
627         # test high 32bits
628         cmpl $0, 12(%edi)
629         jz e820_highzero
630         # high part is non-zero, make low part ffffffff
631         xor %eax, %eax
632         not %eax
633         jmp 0f
634
635 e820_highzero:
636         # if both high and low parts are zero, ignore
637         mov 8(%edi), %eax
638         cmpl $0, %eax
639         jz e820_skip
640
641 0:      mov %eax, 4(%esi,%ebp,8)
642         incl boot_mem_map_size
643
644 e820_skip:
645         # terminate the loop if ebx was reset to 0
646         cmp $0, %ebx
647         jz e820_done
648         jmp e820_looptop
649
650 e820_done:
651         clc
652         ret
653
654 e820_fail:
655         # if size > 0, then it's not a failure, just the end
656         cmpl $0, boot_mem_map_size
657         jnz e820_done
658
659         stc
660         ret
661
662
663         # detect extended memory using BIOS call 15h/e801
664 detect_mem_e801:
665         mov $boot_mem_map, %esi
666         mov boot_mem_map_size, %ebp
667         movl $0, (%ebp)
668
669         xor %cx, %cx
670         xor %dx, %dx
671         mov $0xe801, %ax
672         int $0x15
673         jc e801_fail
674
675         cmp $0, %cx
676         jnz 0f
677         cmp $0, %ax
678         jz e801_fail
679         mov %ax, %cx
680         mov %bx, %dx
681
682 0:      movl $0x100000, (%esi)
683         movzx %cx, %eax
684         # first size is in KB, convert to bytes
685         shl $10, %eax
686         mov %eax, 4(%esi)
687         cmp $0, %dx
688         incl boot_mem_map_size
689         jz e801_done
690         movl $0x1000000, 8(%esi)
691         movzx %dx, %eax
692         # second size is in 64kb blocks, convert to bytes
693         shl $16, %eax
694         mov %eax, 12(%esi)
695         incl boot_mem_map_size
696 e801_done:
697         clc
698         ret
699 e801_fail:
700         stc
701         ret
702
703 detect_mem_88:
704         # reportedly some BIOS implementations fail to clear CF on success
705         clc
706         mov $0x88, %ah
707         int $0x15
708         jc x88_fail
709
710         cmp $0, %ax
711         jz x88_fail
712
713         # ax has size in KB, convert to bytes in eax
714         and $0xffff, %eax
715         shl $10, %eax
716
717         mov $boot_mem_map, %esi
718         movl $0x100000, (%esi)
719         mov %eax, 4(%esi)
720
721         movl $1, boot_mem_map_size
722         clc
723         ret
724
725 x88_fail:
726         stc
727         ret
728
729
730         .global boot_mem_map_size
731 boot_mem_map_size: .long 0
732         .global boot_mem_map
733 boot_mem_map: .space 128
734
735
736 # this is not boot loader code. It's called later on by the main kernel
737 # code in 32bit protected mode. It's placed here because it needs to be
738 # located in base memory as it returns and runs in real mode.
739         .code32
740         .align 4
741         # place to save the protected mode IDTR pseudo-descriptor
742         # with sidt, so that it can be restored before returning
743         .short 0
744 saved_idtr:
745 idtlim: .short 0
746 idtaddr:.long 0
747         # real mode IDTR pseudo-descriptor pointing to the IVT at addr 0
748         .short 0
749 rmidt:  .short 0x3ff
750         .long 0
751
752 saved_esp: .long 0
753 saved_ebp: .long 0
754
755         # drop back to unreal mode to call 16bit interrupt
756         .global int86
757 int86:
758         push %ebp
759         mov %esp, %ebp
760         pushal
761         cli
762         # save protected mode IDTR and replace it with the real mode vectors
763         sidt (saved_idtr)
764         lidt (rmidt)
765
766         # modify the int instruction do this here before the
767         # cs-load jumps, to let them flush the instruction cache
768         mov $int_op, %ebx
769         movb 8(%ebp), %al
770         movb %al, 1(%ebx)
771
772         # long jump to load code selector for 16bit code (6)
773         ljmp $0x30,$0f
774 0:
775         .code16
776         # disable protection
777         mov %cr0, %eax
778         and $0xfffe, %ax
779         mov %eax, %cr0
780         # load cs <- 0
781         ljmp $0,$0f
782 0:      # zero data segments
783         xor %ax, %ax
784         mov %ax, %ds
785         mov %ax, %es
786         mov %ax, %ss
787         nop
788
789         # load registers from the int86regs struct
790         mov %esp, saved_esp
791         mov %ebp, saved_ebp
792         mov 12(%ebp), %esp
793         popal
794         mov saved_esp, %esp
795
796         # call 16bit interrupt
797 int_op: int $0
798
799         mov saved_ebp, %ebp
800         mov 12(%ebp), %esp
801         add $34, %esp
802         pushfw
803         pushal
804         mov saved_esp, %esp
805
806         # re-enable protection
807         mov %cr0, %eax
808         or $1, %ax
809         mov %eax, %cr0
810         # long jump to load code selector for 32bit code (1)
811         ljmp $0x8,$0f
812 0:
813         .code32
814         # set data selector (2) to all segment regs
815         mov $0x10, %ax
816         mov %ax, %ds
817         mov %ax, %es
818         mov %ax, %ss
819         nop
820
821         # restore 32bit interrupt descriptor table
822         lidt (saved_idtr)
823         sti
824         popal
825         pop %ebp
826         ret
827
828
829         # buffer used by the track loader ... to load tracks.
830         .align 16
831 buffer: