1 # 256boss - bootable launcher for 256byte intros
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
18 # plus some other code that needs to run below 1mb (int86 implementation).
23 .set main_load_addr, 0x100000
24 .set drive_number, 0x7bec
26 # make sure any BIOS call didn't re-enable interrupts
31 mov %eax, boot_drive_number
43 # enable A20 address line
46 # detect available memory
49 # load the whole program into memory starting at 1MB
57 # enter protected mode for the first time
61 # inter-segment jump to set cs selector to segment 1
65 # set all data selectors to segment 2
91 gdt: # 0: null segment
94 # 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
97 # 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
104 # trap gate 13: general protection fault
107 # type: trap, present, default
111 gpf_msg: .asciz "GP fault "
127 # use the same GDT above, will use data segment: 2
154 mainsz_msg: .asciz "Main program size: "
155 mainsz_msg2: .asciz " ("
156 mainsz_msg3: .asciz " sectors)\n"
165 movl $main_load_addr, dest_ptr
167 # calculate first sector
168 mov $_boot2_size, %eax
171 # add 1 to account for the boot sector
175 # calculate the first track (first_sect / sect_per_track)
176 movzxw sect_per_track, %ecx
180 # remainder is sector within track
183 mov $mainsz_msg, %esi
185 mov $_main_size, %eax
189 mov $mainsz_msg2, %esi
192 # calculate sector count
198 mov $mainsz_msg3, %esi
201 # read a whole track into the buffer (or partial first track)
203 movzxw sect_per_track, %ecx
208 # debug: print the first 32bits of the track
214 # copy to high memory
224 # other than the first track which might be partial, all the rest start from 0
231 # the BIOS might have enabled interrupts
234 # if we were loaded from floppy, turn all floppy motors off
235 movb drive_number, %bl
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"
257 read_retries: .short 0
260 # set es to the start of the destination buffer to allow reading in
261 # full 64k chunks if necessary
267 movw $3, read_retries
278 # calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
280 movzxw num_heads, %ecx
288 mov $rdhead_msg, %esi
297 # cylinder low byte at ch and high bits at cl[7, 6]
304 mov $rdsect_msg, %esi
308 mov $rdlast_msg, %esi
311 # start sector (1-based) in cl[0, 5]
317 # number of sectors in al
319 # call number (2) in ah
322 movb drive_number, %dl
326 # abort after 3 attempts
330 # error, reset controller and retry
336 mov $rdfail_msg, %esi
344 # reset es to 0 before returning
349 str_read_error: .asciz "Read error while reading track: "
352 mov $str_read_error, %esi
364 # better print routines, since we're not constrainted by the 512b of
390 # this looks retarded. in nasm: [ebx * 2 + edx]
391 mov %al, (%edx, %ebx, 2)
392 movb $7, 1(%edx, %ebx, 2)
401 # expects string pointer in esi
411 # expects number in eax
416 mov $numbuf + 16, %esi
446 # move 80 * 24 lines from b80a0 -> b8000
451 # clear last line (b8f00)
461 # clear with white-on-black spaces
462 mov $0x07200720, %eax
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
477 .set FIFO_ENABLE_CLEAR, 0x07
478 .set MCTL_DTR_RTS_OUT2, 0x0b
479 .set LST_TREG_EMPTY, 0x20
496 # clear and enable fifo
497 mov $FIFO_ENABLE_CLEAR, %al
501 mov $MCTL_DTR_RTS_OUT2, %al
518 # wait until the transmit register is empty
521 and $LST_TREG_EMPTY, %al
532 ena20_msg: .asciz "A20 line enabled\n"
543 # keep trying ... we can't do anything useful without A20 anyway
550 # CF = 1 if A20 test fails (not enabled)
554 movl $0xbaadf00d, (%ebx)
555 movl $0xaabbcc42, (%edx)
556 subl $0xbaadf00d, (%ebx)
559 # enable A20 line through port 0x92 (fast A20)
561 mov $ena20_fast_msg, %esi
569 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
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
579 .set KBC_STAT_OUT_RDY, 0x01
580 .set KBC_STAT_IN_FULL, 0x02
583 mov $ena20_kbd_msg, %esi
587 mov $KBC_CMD_WR_OUTPORT, %al
588 out %al, $KBC_CMD_PORT
591 out %al, $KBC_DATA_PORT
594 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
596 # wait until the keyboard controller is ready to accept another byte
598 in $KBC_STATUS_PORT, %al
599 and $KBC_STAT_IN_FULL, %al
607 mov $memdet_e820_msg, %esi
611 mov $rdfail_msg, %esi
614 mov $memdet_e801_msg, %esi
618 mov $rdfail_msg, %esi
621 mov $memdet_88_msg, %esi
625 mov $rdfail_msg, %esi
629 mov $memdet_fail_msg, %esi
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)... "
646 # detect extended memory using BIOS call 15h/e820
648 movl $0, boot_mem_map_size
652 mov $0x534d4150, %edx
659 cmp $0x534d4150, %eax
662 # skip areas starting above 4GB as we won't be able to use them
666 # only care for type 1 (usable ram), otherwise ignore
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)
676 # skip areas with 0 size (also clamp size to 4gb)
680 # high part is non-zero, make low part ffffffff
686 # if both high and low parts are zero, ignore
691 0: mov %eax, 4(%esi,%ebp,8)
692 incl boot_mem_map_size
695 # terminate the loop if ebx was reset to 0
705 # if size > 0, then it's not a failure, just the end
706 cmpl $0, boot_mem_map_size
713 # detect extended memory using BIOS call 15h/e801
715 mov $boot_mem_map, %esi
716 mov boot_mem_map_size, %ebp
732 0: movl $0x100000, (%esi)
734 # first size is in KB, convert to bytes
737 # overflow means it's >4GB, clamp to 4GB
738 mov $0xffffffff, %eax
740 incl boot_mem_map_size
743 movl $0x1000000, 8(%esi)
745 # second size is in 64kb blocks, convert to bytes
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
760 # reportedly some BIOS implementations fail to clear CF on success
769 # ax has size in KB, convert to bytes in eax
773 mov $boot_mem_map, %esi
774 movl $0x100000, (%esi)
777 movl $1, boot_mem_map_size
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
791 boot_mem_map: .space 128
794 .global boot_drive_number
798 # buffer used by the track loader ... to load tracks.
801 .global low_mem_buffer