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