1 # pcboot - bootable PC demo/game kernel
2 # Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
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.
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.
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/>.
17 # this is the second-stage boot loader
21 .set main_load_addr, 0x100000
23 # make sure any BIOS call didn't re-enable interrupts
34 # enable A20 address line
37 # detect available memory
40 # load the whole program into memory starting at 1MB
51 # enter protected mode for the first time
55 # inter-segment jump to set cs selector to segment 1
59 # set all data selectors to segment 2
85 gdt: # 0: null segment
88 # 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
91 # 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
98 # trap gate 13: general protection fault
101 # type: trap, present, default
105 gpf_msg: .asciz "GP fault "
121 # use the same GDT above, will use data segment: 2
148 mainsz_msg: .asciz "Main program size: "
149 mainsz_msg2: .asciz " ("
150 mainsz_msg3: .asciz " sectors)\n"
159 movl $main_load_addr, dest_ptr
161 # calculate first sector
162 mov $_boot2_size, %eax
165 # add 1 to account for the boot sector
169 # calculate the first track (first_sect / sect_per_track)
170 movzxw sect_per_track, %ecx
174 # remainder is sector within track
177 mov $mainsz_msg, %esi
179 mov $_main_size, %eax
183 mov $mainsz_msg2, %esi
186 # calculate sector count
192 mov $mainsz_msg3, %esi
195 # read a whole track into the buffer (or partial first track)
197 movzxw sect_per_track, %ecx
202 # debug: print the first 32bits of the track
208 # copy to high memory
218 # other than the first track which might be partial, all the rest start from 0
225 # the BIOS might have enabled interrupts
228 # just in case we were loaded from floppy, turn all floppy motors off
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"
247 read_retries: .short 0
249 .set drive_number, 0x7bec
251 # set es to the start of the destination buffer to allow reading in
252 # full 64k chunks if necessary
258 movw $3, read_retries
269 # calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
271 movzxw num_heads, %ecx
279 mov $rdhead_msg, %esi
288 # cylinder low byte at ch and high bits at cl[7, 6]
295 mov $rdsect_msg, %esi
299 mov $rdlast_msg, %esi
302 # start sector (1-based) in cl[0, 5]
308 # number of sectors in al
310 # call number (2) in ah
313 movb drive_number, %dl
317 # abort after 3 attempts
321 # error, reset controller and retry
327 mov $rdfail_msg, %esi
335 # reset es to 0 before returning
340 str_read_error: .asciz "Read error while reading track: "
343 mov $str_read_error, %esi
355 # better print routines, since we're not constrainted by the 512b of
381 # this looks retarded. in nasm: [ebx * 2 + edx]
382 mov %al, (%edx, %ebx, 2)
383 movb $7, 1(%edx, %ebx, 2)
392 # expects string pointer in esi
402 # expects number in eax
407 mov $numbuf + 16, %esi
437 # move 80 * 24 lines from b80a0 -> b8000
442 # clear last line (b8f00)
457 .set UART_DATA, 0x3f8
458 .set UART_LSTAT, 0x3fd
459 .set LST_TREG_EMPTY, 0x20
472 # wait until the transmit register is empty
475 and $LST_TREG_EMPTY, %al
486 ena20_msg: .asciz "A20 line enabled\n"
497 # keep trying ... we can't do anything useful without A20 anyway
504 # CF = 1 if A20 test fails (not enabled)
508 movl $0xbaadf00d, (%ebx)
509 movl $0xaabbcc42, (%edx)
510 subl $0xbaadf00d, (%ebx)
513 # enable A20 line through port 0x92 (fast A20)
515 mov $ena20_fast_msg, %esi
523 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
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
533 .set KBC_STAT_OUT_RDY, 0x01
534 .set KBC_STAT_IN_FULL, 0x02
537 mov $ena20_kbd_msg, %esi
541 mov $KBC_CMD_WR_OUTPORT, %al
542 out %al, $KBC_CMD_PORT
545 out %al, $KBC_DATA_PORT
548 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
550 # wait until the keyboard controller is ready to accept another byte
552 in $KBC_STATUS_PORT, %al
553 and $KBC_STAT_IN_FULL, %al
561 mov $memdet_e820_msg, %esi
565 mov $rdfail_msg, %esi
568 mov $memdet_e801_msg, %esi
572 mov $rdfail_msg, %esi
575 mov $memdet_88_msg, %esi
579 mov $rdfail_msg, %esi
583 mov $memdet_fail_msg, %esi
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)... "
600 # detect extended memory using BIOS call 15h/e820
602 movl $0, boot_mem_map_size
606 mov $0x534d4150, %edx
613 cmp $0x534d4150, %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)
622 # only care for type 1 (usable ram), otherwise ignore
626 # skip areas with 0 size (also clamp size to 4gb)
630 # high part is non-zero, make low part ffffffff
636 # if both high and low parts are zero, ignore
641 0: mov %eax, 4(%esi,%ebp,8)
642 incl boot_mem_map_size
645 # terminate the loop if ebx was reset to 0
655 # if size > 0, then it's not a failure, just the end
656 cmpl $0, boot_mem_map_size
663 # detect extended memory using BIOS call 15h/e801
665 mov $boot_mem_map, %esi
666 mov boot_mem_map_size, %ebp
682 0: movl $0x100000, (%esi)
684 # first size is in KB, convert to bytes
688 incl boot_mem_map_size
690 movl $0x1000000, 8(%esi)
692 # second size is in 64kb blocks, convert to bytes
695 incl boot_mem_map_size
704 # reportedly some BIOS implementations fail to clear CF on success
713 # ax has size in KB, convert to bytes in eax
717 mov $boot_mem_map, %esi
718 movl $0x100000, (%esi)
721 movl $1, boot_mem_map_size
730 .global boot_mem_map_size
731 boot_mem_map_size: .long 0
733 boot_mem_map: .space 128
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.
741 # place to save the protected mode IDTR pseudo-descriptor
742 # with sidt, so that it can be restored before returning
747 # real mode IDTR pseudo-descriptor pointing to the IVT at addr 0
755 # drop back to unreal mode to call 16bit interrupt
762 # save protected mode IDTR and replace it with the real mode vectors
766 # modify the int instruction do this here before the
767 # cs-load jumps, to let them flush the instruction cache
772 # long jump to load code selector for 16bit code (6)
782 0: # zero data segments
789 # load registers from the int86regs struct
796 # call 16bit interrupt
806 # re-enable protection
810 # long jump to load code selector for 32bit code (1)
814 # set data selector (2) to all segment regs
821 # restore 32bit interrupt descriptor table
829 # buffer used by the track loader ... to load tracks.