fernlader/lboot.S

705 lines
20 KiB
ArmAsm
Raw Normal View History

2025-07-04 03:10:55 +02:00
// vim: et:sw=12:ts=12:sts=12
// lboot.S: boot code for legacy BIOS boot over PXE.
2025-07-04 03:10:55 +02:00
2025-07-08 02:04:02 +02:00
/* The assembly code in this project follows its own
* made up calling convention, because it is more convenient
* than the conventional ones for working in unreal mode.
*
* Callees preserve all registers,
* except those which they use to return values.
* All 32 bits of a register are preserved.
* Saved registers are push to the stack before the frame pointer,
* as this simplifies frame cleanup.
* Addresses/pointers are returned in EBX,
* all other values are returned in EAX.
*
* DS, ES have a 4Gb limit, and an offset of 0.
* CS has an offset of 0. SS is unspecified.
*/
2025-07-04 03:10:55 +02:00
.global _start
.text
2025-07-04 04:02:05 +02:00
.code16
2025-07-04 03:10:55 +02:00
2025-07-08 02:04:02 +02:00
.set PACKET_SIZE, 512
2025-07-09 18:49:38 +02:00
.set TX_BUF_SIZE, 4096
2025-07-07 02:10:02 +02:00
.macro pxe_call, opcode
2025-07-08 02:04:02 +02:00
push %eax
push %ebx
push %ecx
push %edx
2025-07-07 02:10:02 +02:00
mov %sp, %bx
2025-07-08 02:04:02 +02:00
add $16, %bx
2025-07-07 02:10:02 +02:00
push %ss
push %bx
push $\opcode
lcall *pxe_api
add $6, %sp
2025-07-08 02:04:02 +02:00
2025-07-09 18:49:38 +02:00
mov %ss:(%bx), %cx
or %cx, %ax
2025-07-07 02:10:02 +02:00
jnz _pcerr
2025-07-08 02:04:02 +02:00
pop %edx
pop %ecx
pop %ebx
pop %eax
2025-07-07 02:10:02 +02:00
.endm
// _start: entry point
2025-07-04 15:29:41 +02:00
_start: cli
cld
2025-07-07 02:10:02 +02:00
mov %sp, %bp
2025-07-04 15:51:51 +02:00
xor %ax, %ax
2025-07-04 04:02:05 +02:00
mov %ax, %ds
2025-07-08 02:04:02 +02:00
mov %ax, %es
2025-07-04 04:02:05 +02:00
2025-07-04 16:17:38 +02:00
call init_com1
mov $msg_start, %si
call print
2025-07-04 15:51:51 +02:00
mov $msg_start, %si
2025-07-04 15:29:41 +02:00
call print
2025-07-07 02:10:02 +02:00
mov %ss:4(%bp), %si
mov %ss:6(%bp), %ax
2025-07-08 02:04:02 +02:00
mov %ax, %fs
2025-07-07 02:10:02 +02:00
.set PXE_MAGIC, 0x45585021
2025-07-08 02:04:02 +02:00
cmpl $PXE_MAGIC, %fs:(%si)
2025-07-07 02:10:02 +02:00
je 1f
mov $msg_pserr, %si
call print
jmp hang
2025-07-08 02:04:02 +02:00
1: mov %fs:16(%si), %eax
2025-07-07 02:10:02 +02:00
mov %eax, pxe_api
2025-07-04 15:51:51 +02:00
mov $msg_a20, %si
call print
2025-07-04 15:29:41 +02:00
call enable_a20
2025-07-09 18:49:38 +02:00
.set PXE_GET_CACHED_INFO, 0x0071
push $0
push %cs
push $tx_buf
push $TX_BUF_SIZE
push $2
push $0
pxe_call PXE_GET_CACHED_INFO
add $12, %sp
mov tx_buf+20, %eax
mov %eax, server_ip
2025-07-09 19:29:27 +02:00
mov tx_buf+24, %eax
mov %eax, gateway_ip
2025-07-09 18:49:38 +02:00
2025-07-04 15:51:51 +02:00
mov $msg_unreal, %si
2025-07-04 04:02:05 +02:00
call print
2025-07-04 15:51:51 +02:00
call unreal
2025-07-04 15:29:41 +02:00
mov $msg_getmap, %si
2025-07-04 16:33:05 +02:00
call print
2025-07-05 19:39:25 +02:00
call get_map
2025-07-05 19:30:14 +02:00
mov $msg_mkheap, %si
call print
2025-07-05 19:39:25 +02:00
call make_heap
2025-07-05 19:30:14 +02:00
mov $msg_paging, %si
call print
call paging
2025-07-04 16:33:05 +02:00
2025-07-07 02:10:02 +02:00
mov $msg_read, %si
call print
2025-07-08 02:04:02 +02:00
mov $fn_initrd, %esi
2025-07-07 02:10:02 +02:00
call read_file
2025-07-08 15:59:52 +02:00
mov %ebx, bb_ird_ptr
2025-07-07 02:10:02 +02:00
2025-07-05 20:29:53 +02:00
mov $msg_long, %si
2025-07-04 15:51:51 +02:00
call print
2025-07-05 20:29:53 +02:00
call long
jmp hang
2025-07-04 04:02:05 +02:00
2025-07-05 20:41:51 +02:00
.set COM1, 0x3F8
2025-07-07 02:10:02 +02:00
.macro com1_write offset=0, byte
2025-07-04 16:17:38 +02:00
mov $COM1+\offset, %dx
mov $\byte, %al
outb %al, %dx
2025-07-07 02:10:02 +02:00
.endm
2025-07-04 16:17:38 +02:00
// init_com1: Set up COM1 port for debug output
2025-07-04 16:17:38 +02:00
init_com1: com1_write 1, 0x00 // clear interrupts
com1_write 3, 0x80 // set DLAB to 1
com1_write 0, 0x0C // 9600 baud rate
com1_write 1, 0x00
com1_write 3, 0x07 // 8 bit data + 1 parity bit
ret
2025-07-07 16:18:46 +02:00
// enable_a20: Allow use of 'high' (>1Mb) memory
enable_a20: // Of all the ways to toggle A20, we only try the Fast A20 Gate.
// This is known to cause problems on some ancient systems,
// but as our bootloader exclusively runs on 64-bit machines,
// we should not run into any of those systems.
// Modern machines apparently don't even have the A20 line anymore.
2025-07-04 15:29:41 +02:00
inb $0x92, %al
or $2, %al
outb %al, $0x92
ret
// unreal: Enter unreal mode
2025-07-04 15:51:51 +02:00
unreal: push %ds
2025-07-08 02:04:02 +02:00
push %es
2025-07-05 20:41:51 +02:00
lgdt gdt16_ptr
2025-07-04 15:51:51 +02:00
mov %cr0, %eax
or $0x01, %al
2025-07-04 15:29:41 +02:00
mov %eax, %cr0
2025-07-05 19:39:25 +02:00
ljmp $0x8, $_urprot
2025-07-04 15:51:51 +02:00
2025-07-05 19:39:25 +02:00
_urprot: mov $0x10, %cx
2025-07-04 15:51:51 +02:00
mov %cx, %ds
2025-07-08 02:04:02 +02:00
mov %cx, %es
2025-07-04 15:29:41 +02:00
2025-07-04 15:51:51 +02:00
and $0xFE, %al
mov %eax, %cr0
2025-07-05 19:39:25 +02:00
ljmp $0x0, $_urunreal
2025-07-04 15:51:51 +02:00
2025-07-08 02:04:02 +02:00
_urunreal: pop %es
pop %ds
2025-07-04 15:51:51 +02:00
ret
2025-07-05 19:39:25 +02:00
// get_map: Retrieve memory map using e820 BIOS function
get_map: mov $bb_memmap, %edi
2025-07-04 16:33:05 +02:00
xor %ebx, %ebx
mov $0x534D4150, %edx // e820 magic number
2025-07-05 19:39:25 +02:00
_gmnext: movl $0, 20(%di)
2025-07-04 16:33:05 +02:00
mov $24, %ecx
mov $0xE820, %eax
int $0x15
2025-07-05 19:39:25 +02:00
jc _gmdone
test %ebx, %ebx
2025-07-05 19:39:25 +02:00
jz _gmdone
add $24, %di
2025-07-05 19:39:25 +02:00
jmp _gmnext
_gmdone: add $24, %di
sub $bootboot, %edi
mov %edi, bb_size
ret
// paging: Set up initial page tables for long mode
2025-07-08 02:04:02 +02:00
paging:
mov heap_ptr, %eax
2025-07-08 14:15:51 +02:00
mov %eax, %ecx
add $6*4096, %ecx
cmp heap_end, %ecx
2025-07-08 02:04:02 +02:00
ja out_of_mem
2025-07-08 14:15:51 +02:00
mov %ecx, heap_ptr
2025-07-08 02:04:02 +02:00
mov %eax, pd_ptr
2025-07-08 02:04:02 +02:00
add $4*4096, %eax
mov %eax, pdp_ptr
2025-07-08 02:04:02 +02:00
add $4096, %eax
mov %eax, pml4_ptr
// fill PDEs with identity map < 4Gb
mov pd_ptr, %edi
xor %ecx, %ecx
_pgnext: mov %ecx, %eax
shl $21, %eax
or $0b10000011, %eax
movl %eax, 0(%edi)
movl $0, 4(%edi)
add $8, %edi
inc %ecx
cmp $4*512, %ecx
jne _pgnext
// link to PDs in PDP
mov pdp_ptr, %edi
mov pd_ptr, %eax
orl $0b11, %eax
movl %eax, 0(%edi)
movl $0, 4(%edi)
add $0x1000, %eax
movl %eax, 8(%edi)
movl $0, 12(%edi)
add $0x1000, %eax
movl %eax, 16(%edi)
movl $0, 20(%edi)
add $0x1000, %eax
movl %eax, 24(%edi)
movl $0, 28(%edi)
// link to PDP in PML4
mov pml4_ptr, %edi
mov pdp_ptr, %eax
orl $0b11, %eax
movl %eax, 0(%edi)
movl $0, 4(%edi)
2025-07-04 16:33:05 +02:00
ret
2025-07-05 19:39:25 +02:00
// make_heap: find a memory range suitable for heap usage
2025-07-08 17:22:00 +02:00
make_heap: mov $bb_memmap-24, %esi
2025-07-05 19:30:14 +02:00
_mhnext: add $24, %si
mov $bootboot, %eax
add bb_size, %eax
cmp %ax, %si
2025-07-05 19:30:14 +02:00
jae _mhdone
cmpl $1, 16(%si)
jne _mhnext
cmpl $0, 4(%si)
ja _mhnext
2025-07-08 02:04:02 +02:00
mov 0(%si), %eax
2025-07-05 19:30:14 +02:00
mov 8(%si), %ecx
// find end of range, clip to 4Gb
2025-07-08 02:04:02 +02:00
add %eax, %ecx
2025-07-05 19:30:14 +02:00
jnc 1f
mov $0xFFFFFFFF, %ecx
// handle wraparound if length > 4Gb
1: cmpl $0, 12(%si)
je 1f
mov $0xFFFFFFFF, %ecx
// adjust base to above 1Mb, above the bootloader
2025-07-08 02:04:02 +02:00
1: cmp $0x10000, %eax
2025-07-05 19:30:14 +02:00
jae 1f
2025-07-08 02:04:02 +02:00
mov $0x10000, %eax
// align start to 4Kb boundary
1: add $0xFFF, %eax
and $0xFFFFF000, %eax
// Is this entry larger than current heap?
mov %ecx, %edx
sub %eax, %edx
mov heap_end, %ebx
sub heap_ptr, %ebx
cmp %ebx, %edx
2025-07-05 19:30:14 +02:00
jbe _mhnext
2025-07-08 02:04:02 +02:00
// If so, switch to it.
mov %eax, heap_ptr
mov %ecx, heap_end
2025-07-08 17:22:00 +02:00
mov %esi, heap_mment
2025-07-05 19:30:14 +02:00
jmp _mhnext
_mhdone: ret
2025-07-08 02:04:02 +02:00
.set PXE_TFTP_OPEN, 0x0020
.set PXE_TFTP_CLOSE, 0x0021
.set PXE_TFTP_READ, 0x0022
read_file: push %bp
mov %sp, %bp
call tftp_open
mov heap_ptr, %ebx
mov %ebx, %edi
_rdloop: call tftp_read
mov $tx_buf, %esi
mov $PACKET_SIZE, %ecx
addr32 rep movsb
cmp heap_end, %edi
ja out_of_mem
cmp $PACKET_SIZE, %ax
je _rdloop
mov %edi, heap_ptr
call tftp_close
leave
ret
2025-07-08 02:04:02 +02:00
// tftp_open: Open a file stream over TFTP.
// A pointer to the filename is passed in ESI.
tftp_open: push %ecx
push %esi
push %edi
push %bp
mov %sp, %bp
2025-07-09 19:29:27 +02:00
mov %esi, %ebx
mov $msg_topen, %si
call print
push $PACKET_SIZE
push $69<<8
2025-07-08 02:04:02 +02:00
mov $128, %ecx
2025-07-09 19:29:27 +02:00
sub %cx, %sp
mov %ss, %edi
shl $4, %edi
add %esp, %edi
xor %ax, %ax
cld
addr32 rep stosb
mov %ebx, %esi
mov $6, %ecx
2025-07-08 02:04:02 +02:00
mov %ss, %edi
shl $4, %edi
add %esp, %edi
2025-07-09 19:29:27 +02:00
cld
2025-07-08 02:04:02 +02:00
addr32 rep movsb
2025-07-09 19:29:27 +02:00
push gateway_ip+2
push gateway_ip
2025-07-08 02:04:02 +02:00
push server_ip+2
push server_ip
push $0
mov %ss, %esi
shl $4, %esi
add %esp, %esi
mov $128, %ecx
call dump
2025-07-08 02:04:02 +02:00
pxe_call PXE_TFTP_OPEN
2025-07-07 02:10:02 +02:00
2025-07-08 02:04:02 +02:00
leave
pop %edi
pop %esi
pop %ecx
ret
2025-07-07 02:10:02 +02:00
2025-07-08 02:04:02 +02:00
// tftp_close: Close the TFTP stream.
tftp_close: push %bp
mov %sp, %bp
2025-07-09 19:29:27 +02:00
mov $msg_tclose, %si
call print
2025-07-07 02:10:02 +02:00
push $0
2025-07-08 02:04:02 +02:00
pxe_call PXE_TFTP_CLOSE
2025-07-07 02:10:02 +02:00
2025-07-08 02:04:02 +02:00
leave
ret
2025-07-07 02:55:26 +02:00
2025-07-08 02:04:02 +02:00
// tftp_read: Read the next TFTP packet into tx_buf, return size in AX.
tftp_read: push %ebx
push %bp
mov %sp, %bp
2025-07-07 02:55:26 +02:00
2025-07-09 19:29:27 +02:00
mov $msg_tread, %si
call print
2025-07-07 02:55:26 +02:00
push %cs
push $tx_buf
push $0
push $0
push $0
pxe_call PXE_TFTP_READ
2025-07-08 02:04:02 +02:00
mov %sp, %bx
mov %ss:4(%bx), %ax
2025-07-07 02:55:26 +02:00
2025-07-08 02:04:02 +02:00
leave
pop %ebx
ret
2025-07-07 02:55:26 +02:00
2025-07-08 02:04:02 +02:00
out_of_mem: mov $msg_memerr, %si
2025-07-07 02:55:26 +02:00
call print
2025-07-08 02:04:02 +02:00
jmp hang
2025-07-07 02:10:02 +02:00
// print: print NUL-terminated string pointed to by SI
2025-07-09 19:29:27 +02:00
print: push %eax
push %si
2025-07-09 19:29:27 +02:00
push %bp
mov %sp, %bp
1: lodsb
or %al, %al
jz 2f
call printch
jmp 1b
2: leave
pop %si
pop %eax
ret
2025-07-09 19:29:27 +02:00
printch: push %eax
push %ebx
push %edx
push %ebp
2025-07-04 04:02:05 +02:00
mov %ax, %bx
1: mov $COM1+5, %dx
2025-07-04 16:17:38 +02:00
inb %dx, %al
test $0x20, %al
jz 1b
2025-07-04 16:17:38 +02:00
mov %bx, %ax
2025-07-04 16:17:38 +02:00
mov $COM1, %dx
outb %al, %dx
xor %bx, %bx
2025-07-04 03:10:55 +02:00
mov $0x0E, %ah
int $0x10
2025-07-04 04:02:05 +02:00
pop %ebp
pop %edx
pop %ebx
pop %eax
ret
dump: push %eax
push %ebx
push %ecx
push %edx
push %esi
push %bp
mov %sp, %bp
xor %dx, %dx
1: cmp %cx, %dx
jae 2f
inc %dx
addr32 lodsb
push %ax
mov %al, %bl
shr $4, %bx
and $0x0F, %bx
mov hex_digits(%bx), %al
call printch
pop %bx
and $0x0F, %bx
mov hex_digits(%bx), %al
call printch
mov $' ', %al
call printch
test $0xF, %dx
jnz 1b
mov $'\r', %al
call printch
mov $'\n', %al
call printch
jmp 1b
2: mov $'\r', %al
call printch
mov $'\n', %al
call printch
leave
2025-07-09 19:29:27 +02:00
pop %esi
pop %edx
pop %ecx
2025-07-09 19:29:27 +02:00
pop %ebx
pop %eax
ret
2025-07-04 15:29:41 +02:00
2025-07-05 20:29:53 +02:00
// long: Enter long mode
long:
// Enable PAE
mov %cr4, %eax
or $0b100000, %eax
mov %eax, %cr4
// Load page table
mov pml4_ptr, %eax
mov %eax, %cr3
// Enable long mode
2025-07-05 20:41:51 +02:00
.set IA32_EFER, 0xC0000080
2025-07-05 20:29:53 +02:00
mov $IA32_EFER, %ecx
rdmsr
or $0x100, %eax
wrmsr
// Enable protected mode + paging
mov %cr0, %eax
or $0x80000001, %eax
mov %eax, %cr0
// Linearize stack address
mov %ss, %eax
shl $4, %eax
add %eax, %esp
mov %esp, %ebp
2025-07-05 20:41:51 +02:00
// Load long mode GDT, switch to 64-bit CS
lgdt gdt64_ptr
ljmp $0x8, $trampo64
2025-07-05 20:29:53 +02:00
// hang: sleep indefinitely
hang: hlt
jmp hang
2025-07-09 19:29:27 +02:00
_pcerr: mov $msg_pcerr, %si
2025-07-09 18:49:38 +02:00
call print
2025-07-07 02:10:02 +02:00
jmp hang
2025-07-05 20:41:51 +02:00
// gdt16: Protected mode / Unreal mode 16-bit GDT
gdt16: // entry 0: null descriptor
2025-07-04 15:29:41 +02:00
.word 0
.word 0
.byte 0
.byte 0
.byte 0
.byte 0
// entry 1: code segment
.word 0xFFFF
.word 0
.byte 0
.byte 0b10011010
.byte 0x8F
.byte 0
// entry 2: data segment
.word 0xFFFF
.word 0
.byte 0
.byte 0b10010010
.byte 0x8F
.byte 0
2025-07-05 20:41:51 +02:00
.set gdt16_size, .-gdt16
gdt16_ptr: .word gdt16_size-1
.long gdt16
// gdt64: Long mode 64-bit GDT
gdt64: // entry 0: null descriptor
.quad 0
// entry 1: code segment
.word 0
.word 0
.byte 0
.byte 0x98
.byte 0x60
.byte 0
// entry 2: data segment
.word 0
.word 0
.byte 0
.byte 0x92
2025-07-05 20:41:51 +02:00
.byte 0x00
.byte 0
.set gdt64_size, .-gdt64
gdt64_ptr: .word gdt64_size-1
.quad gdt64
2025-07-04 04:02:05 +02:00
// Messages to print
2025-07-04 15:51:51 +02:00
msg_start: .asciz "Netboot via fernlader v1 ...\r\n"
msg_a20: .asciz " * Enabling A20\r\n"
msg_unreal: .asciz " * Unreal Mode\r\n"
msg_getmap: .asciz " * Memory Map\r\n"
2025-07-05 19:30:14 +02:00
msg_mkheap: .asciz " * Making Space\r\n"
msg_paging: .asciz " * Paging\r\n"
2025-07-07 02:10:02 +02:00
msg_read: .asciz " * Retrieving\r\n"
2025-07-05 20:29:53 +02:00
msg_long: .asciz " * Long Mode\r\n"
2025-07-07 02:10:02 +02:00
msg_pserr: .asciz "panic: Missing !PXE structure.\r\n"
msg_pcerr: .asciz "panic: PXE call failed.\r\n"
2025-07-08 02:04:02 +02:00
msg_memerr: .asciz "panic: Out of heap space.\r\n"
2025-07-09 19:29:27 +02:00
msg_topen: .asciz "tftp_open\r\n"
msg_tclose: .asciz "tftp_close\r\n"
msg_tread: .asciz "tftp_read\r\n"
2025-07-08 02:04:02 +02:00
fn_initrd: .asciz "initrd"
2025-07-07 02:10:02 +02:00
pxe_api: .long 0
server_ip: .space 4
2025-07-09 19:29:27 +02:00
gateway_ip: .space 4
2025-07-07 02:10:02 +02:00
2025-07-08 02:04:02 +02:00
heap_ptr: .long 0
heap_end: .long 0
2025-07-08 17:22:00 +02:00
heap_mment: .long tx_buf
2025-07-08 02:04:02 +02:00
// Long mode initial page tables
pd_ptr: .long 0
pdp_ptr: .long 0
pml4_ptr: .long 0
// Points to the end of the memory map
memmap_end: .short 0
2025-07-09 18:49:38 +02:00
tx_buf: .space TX_BUF_SIZE
2025-07-07 02:10:02 +02:00
.code64
// trampo64: Trampoline function to load long-mode segments
// before entering the loader.
trampo64:
mov $0x10, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
2025-07-07 02:10:02 +02:00
mov %eax, %ss
2025-07-08 02:04:02 +02:00
2025-07-08 17:22:00 +02:00
// Remove our heap from the memory map so it doesn't get overwritten
mov heap_mment, %esi
mov 8(%rsi), %rcx
add 0(%rsi), %rcx
mov heap_ptr, %eax
sub %rax, %rcx
mov %rax, 0(%rsi)
mov %rcx, 8(%rsi)
// Mangle e820 memmap into bootboot's format
mov $bb_memmap, %rsi
mov $bb_memmap, %rdi
mov $bootboot, %edx
add bb_size, %edx
1:
mov 0(%rsi), %rax
mov 8(%rsi), %rcx
mov 16(%rsi), %ebx
cmp $6, %ebx
jb 2f
mov $2, %ebx
2: mov type_table(%rbx), %bl
2025-07-08 15:59:52 +02:00
and $-16, %rcx
or %rbx, %rcx
mov %rax, 0(%rdi)
mov %rcx, 8(%rdi)
add $24, %rsi
add $16, %rdi
cmp %rdx, %rsi
jb 1b
sub $bootboot, %edi
mov %edi, bb_size
mov $bootboot, %edi
jmp loader_main
2025-07-07 02:10:02 +02:00
type_table: .byte 0
.byte 1
.byte 0
.byte 2
.byte 2
.byte 0
2025-07-07 02:10:02 +02:00
// ToDo List:
// - Sorting the memmap
// - Sanitizing the memmap
// - Parsing a config file
2025-07-09 18:49:38 +02:00
hex_digits: .ascii "0123456789ABCDEF"
.section .data.bootboot
bootboot: .ascii "BOOT"
bb_size: .long 128
.byte 1
.byte 0
.short 1
.short 0
.space 10, 0
bb_ird_ptr: .quad 0
bb_ird_size:.quad 0
.space 24, 0
.space 64, 0
bb_memmap:
.space 4096-128, 0