do not edit — generated by btf.
git.druid.rocksindexdruid520kaboomsrc/boot/dynamite.s

src/boot/dynamite.s


/*
 * dynamite: kaboom's real bios bootloader. bios loads this exact
 * 512-byte sector at 0x7c00 in 16-bit real mode and jumps to it with
 * dl = the boot drive number -- that's the entire contract a real
 * bios gives us. everything past that is this file's own job: enable
 * a20, read the kernel off disk in bios-safe chunks, get into 32-bit
 * protected mode, relocate the kernel up to its real load address,
 * and jump to it. matches boot.s's own note on why this class of code
 * is the single highest-risk in the whole project -- one wrong byte
 * here triple-faults silently, no error message, real hardware and
 * qemu alike.
 *
 * KERNEL_BLOB_DWORDS, KERNEL_LOAD_ADDR and KERNEL_ENTRY_ADDR are never
 * hand-computed or hardcoded here -- mk/bl.sh passes them in as --defsym
 * values, read straight off the actual built out/kaboom.elf at build time (its
 * real entry address, and the raw blob's real size rounded up to a
 * dword), so this file never depends on a guessed offset that could
 * silently drift out of sync with a real build.
 *
 * two real, known, out-of-scope-to-fix limitations, noted here rather
 * than coded around -- neither is meaningfully testable without real
 * hardware to try it against:
 *   1. there is no mbr partition table -- lba 0 is dynamite's own
 *      512-byte sector in full, not a partitioned disk. fine for a
 *      dedicated boot device (a usb flash drive imaged with dd), but
 *      this image should never be presented as one partition among
 *      several to a bios/os expecting a partition table.
 *   2. some older bioses' usb-floppy-emulation mode (distinct from the
 *      far more common usb-hdd/"superfloppy" emulation mode most
 *      modern bioses use) patch bytes 0x03-0x3d of the boot sector
 *      (the classic fat12/16 bpb region) before executing it -- since
 *      dynamite's real code occupies that exact byte range (code
 *      starts immediately at offset 0, no bpb reserved), such a bios
 *      would corrupt dynamite's own instructions. a known, real, but
 *      narrow limitation of the current design.
 */
.code16
.global _dynamite_start
_dynamite_start:
	cli
	xorw %ax, %ax
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %ss
	movw $0x7c00, %sp
	sti
 
	movb %dl, boot_drive
 
	/* fast a20 gate (port 0x92) -- every cpu capable of the long mode
	 * kaboom itself needs already supports this, no need for the
	 * older, slower keyboard-controller a20 dance ancient bootloaders
	 * needed. */
	inb $0x92, %al
	orb $0x02, %al
	outb %al, $0x92
 
	/* int 13h ah=41h "extensions present" check, run ONCE here before
	 * the chunked ah=42h read loop begins -- a standard, cheap bios
	 * capability check. some very old bioses don't support the
	 * extended (lba) disk services ah=42h relies on; without this,
	 * such a bios would just get called with ah=42h blindly and
	 * return an undefined/garbage result instead of a clean, visible
	 * failure. reuses the existing disk_error path (the same crude
	 * "DE" red-screen vga signal a failed ah=42h read already uses) --
	 * dl gets reloaded from boot_drive again below for the real int
	 * 13h calls inside read_loop, so nothing here needs to preserve it
	 * across the two calls. */
	movb $0x41, %ah
	movw $0x55aa, %bx
	movb boot_drive, %dl
	int $0x13
	jc disk_error
	cmpw $0xaa55, %bx
	jne disk_error
	/* bit 0 of cx: "extended disk access functions (ah=42h-44h,47h,
	 * 48h) supported" -- the specific subset read_loop's own ah=42h
	 * calls below actually rely on, distinct from the coarser
	 * carry/bx check above (which only confirms extensions of SOME
	 * kind are present). */
	testb $0x01, %cl
	jz disk_error
 
	/* read the whole reserved kernel region (lba 1..1024) in 64-
	 * sector (32kib) chunks, not one single 1024-sector call -- real
	 * bios int 13h ah=42h implementations commonly cap a single
	 * transfer well under 1024 sectors (some as low as 127), and
	 * keeping each chunk's transfer buffer within one real-mode
	 * segment side-steps the classic "transfer crosses a 64kib
	 * segment boundary" bios bug class entirely, for free. 16 chunks
	 * of 64 sectors = 1024 sectors total. */
	movw $16, chunks_left  /* 16*64 = 1024 sectors -- must stay in sync with
	                        * KFS_LBA_BASE (currently 1025 = 1 dynamite
	                        * sector + 1024 kernel-blob sectors) in
	                        * src/fs/kfs.nsc and mk/disk.pl, the same way
	                        * those two already reference each other. */
	movw $1, dap_lba_lo    /* first chunk starts at lba 1, right after us --
	                        * see the KFS_LBA_BASE note just above. */
	movw $0x1000, dap_seg  /* destination segment for chunk 0: 0x1000:0000 = 0x10000 */
 
read_loop:
	movw $dap, %si
	movb $0x42, %ah
	movb boot_drive, %dl
	int $0x13
	jc disk_error
 
	/* advance to the next chunk: +64 sectors on the disk side, +32kib
	 * (0x800 paragraphs) on the memory side. */
	movw dap_lba_lo, %ax
	addw $64, %ax
	movw %ax, dap_lba_lo
	movw dap_seg, %ax
	addw $0x0800, %ax
	movw %ax, dap_seg
 
	decw chunks_left
	jnz read_loop
 
	/* real bios e820 memory map -- same (addr, size, type) 24-byte
	 * entry shape vmm_e820_add (vmm.nsc) already expects, written
	 * directly into a fixed low-memory scratch buffer kmain reads
	 * after the mode transition (same "compute it once in real mode,
	 * hand it forward" pattern this file already uses for
	 * KERNEL_ENTRY_ADDR/KERNEL_LOAD_ADDR). es is still 0 from the very
	 * top of this file, so es:di addresses e820_buf directly as long
	 * as its linked address stays below 0x10000 -- true here since
	 * e820_buf lives in .bss, placed by the linker right after this
	 * file's own 512-byte .text, nowhere near the 0x10000 kernel-blob
	 * scratch segment read_loop just filled. */
	xorl %ebx, %ebx
	movl $e820_buf, %edi
	movl $0, e820_count
e820_loop:
	movl $0x0534d4150, %edx      /* 'SMAP' */
	movl $0xe820, %eax
	movl $24, %ecx
	int $0x15
	jc e820_done                  /* carry set: error or end of list */
	cmpl $0x0534d4150, %eax
	jne e820_done                  /* bios didn't return 'SMAP': stop */
	incl e820_count
	addl $24, %edi
	cmpl $256, e820_count
	jae e820_done                  /* hit the same 256-entry ceiling
	                                 * vmm_e820_add enforces -- stop
	                                 * asking rather than overflow
	                                 * e820_buf */
	testl %ebx, %ebx
	jnz e820_loop                  /* ebx==0 means that was the last entry */
e820_done:
 
	/* interrupts off before the mode switch (intel sdm vol 3, the
	 * protected-mode switch procedure's own step 1). the sti above was
	 * only for the int 13h reads. an irq taken after cr0.pe is set
	 * would be dispatched through idtr, which still points at the
	 * real-mode ivt, read as protected-mode gates: triple fault. the
	 * window runs through the whole rep movsl below and the jmp into
	 * _start, which does its own cli. */
	cli
	lgdt gdt_ptr
	movl %cr0, %eax
	orl $0x1, %eax
	movl %eax, %cr0
	ljmpl $0x08, $protected_mode_entry
 
disk_error:
	/* a crude but real visible failure signal (prints "DE" in white-
	 * on-red directly into vga text memory) rather than a silent
	 * hang indistinguishable from a triple fault -- there's no string-
	 * printing code this early to do anything fancier. */
	movw $0xb800, %ax
	movw %ax, %es
	movw $0x4c44, %es:0x00
	movw $0x4c45, %es:0x02
	cli
hang:
	hlt
	jmp hang
 
.align 4
gdt:
	.quad 0                       /* null descriptor */
	.quad 0x00cf9a000000ffff      /* 0x08: 32-bit code, ring0, flat, 4kib granularity */
	.quad 0x00cf92000000ffff      /* 0x10: 32-bit data, ring0, flat, 4kib granularity */
gdt_end:
gdt_ptr:
	.word gdt_end - gdt - 1
	.long gdt
 
.align 4
dap:                              /* bios int 13h ah=42h disk address packet, 16 bytes */
	.byte 0x10                    /* packet size */
	.byte 0                       /* reserved */
dap_count:
	.word 64                      /* sectors per chunk */
	.word 0x0000                  /* transfer buffer offset, always 0 -- only the segment advances */
dap_seg:
	.word 0                       /* transfer buffer segment, patched per chunk above */
dap_lba_lo:
	.word 0                       /* starting lba (low 16 bits), patched per chunk above */
	.word 0
	.long 0                       /* starting lba (high 32 bits) -- always 0, this disk is nowhere near 4 billion sectors */
 
boot_drive:
	.byte 0
chunks_left:
	.word 0
 
/* e820 scratch buffer -- deliberately .bss, not the default (.text)
 * section everything above lives in: mk/bl.sh's objcopy -j .text pulls
 * ONLY .text into the real 512-byte sector, so a .bss reservation here
 * costs the sector nothing (no bytes emitted) while still getting a
 * real, stable linked address from the same `ld -Ttext=0x7c00` command
 * that lays out everything else in dynamite.elf -- i.e. this is a
 * READ address computed once, by the linker, never a hand-guessed
 * magic number the way this file's own header comment warns against
 * for KERNEL_LOAD_ADDR/KERNEL_ENTRY_ADDR. 256 entries * 24 bytes =
 * 6144, the same ceiling and entry size vmm_e820_add (vmm.nsc)
 * already enforces. */
.section .bss
.align 4
e820_buf:
	.skip 6144
e820_count:
	.long 0
.section .text
 
.code32
protected_mode_entry:
	movw $0x10, %ax
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %ss
	movw %ax, %fs
	movw %ax, %gs
	movl $0x9000, %esp   /* well clear of the 0x10000-0x90000 scratch region the kernel blob was just read into */
 
	/* relocate the whole kernel blob from the low scratch buffer
	 * (0x10000) up to its real home, KERNEL_LOAD_ADDR -- the lowest
	 * load address in out/kaboom.elf, which is where objcopy -O binary
	 * starts the blob (mk/bl.sh reads it off the elf, same as the
	 * entry point). 32-bit addressing has no real-mode-style reach
	 * limit, so this is a plain, ordinary copy. cld: neither a bios
	 * handoff nor anything else guarantees df=0, and a backwards copy
	 * would run down through low memory instead. */
	movl $0x10000, %esi
	movl $KERNEL_LOAD_ADDR, %edi
	movl $KERNEL_BLOB_DWORDS, %ecx
	cld
	rep movsl
 
	/* hand the e820 buffer forward to boot.s's _start the same way
	 * pvh's own entry hands hvm_start_info forward via %ebx: esi/edx
	 * are both free again here (rep movsl is done with them) and
	 * neither is touched by _start before it saves them off, same as
	 * ebx -- see boot.s's own comment on that. %ebp carries an explicit
	 * "this is the real-bios path" sentinel rather than leaving _start
	 * to guess the boot path from whether esi/edx happen to be zero:
	 * the pvh entry ABI only defines %ebx, so nothing guarantees any
	 * other register is reliably zero there, now or in some future
	 * qemu -- an explicit magic beats an inferred one. */
	movl $0xb105b00b, %ebp        /* BIOS BOOT marker, picked to be
	                                 * essentially impossible to collide
	                                 * with whatever pvh's own loader
	                                 * happens to leave in %ebp */
	movl $e820_buf, %esi
	movl e820_count, %edx
 
	jmp KERNEL_ENTRY_ADDR
 
/* boot signature -- bios refuses to treat this sector as bootable at
 * all without these exact two bytes at offset 510/511. */
.org 510
.word 0xaa55
powered by btf.