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