fernlader/lboot.S

445 lines
13 KiB
ArmAsm

// vim: et:sw=12:ts=12:sts=12
.global _start
.text
.code16
.macro pxe_call, opcode
mov %sp, %bx
push %ss
push %bx
push $\opcode
lcall *pxe_api
add $6, %sp
mov %sp, %bx
or %ss:(%bx), %ax
jnz _pcerr
.endm
// _start: entry point
_start: cli
cld
mov %sp, %bp
// we keep our text and data close to each other
xor %ax, %ax
mov %ax, %ds
call init_com1
mov $msg_start, %si
call print
mov %ss:4(%bp), %si
mov %ss:6(%bp), %ax
mov %ax, %es
.set PXE_MAGIC, 0x45585021
cmpl $PXE_MAGIC, %es:(%si)
je 1f
mov $msg_pserr, %si
call print
jmp hang
1: mov %es:16(%si), %eax
mov %eax, pxe_api
mov $msg_a20, %si
call print
call enable_a20
mov $msg_unreal, %si
call print
call unreal
mov $msg_getmap, %si
call print
call get_map
mov $msg_mkheap, %si
call print
call make_heap
mov $msg_paging, %si
call print
call paging
.set PXE_GET_CACHED_INFO, 0x0071
push $0
push %cs
push $tx_buf
push $1024
push $2
push $0
pxe_call PXE_GET_CACHED_INFO
add $12, %sp
mov tx_buf+20, %eax
mov %eax, server_ip
mov $msg_read, %si
call print
call read_file
mov $msg_long, %si
call print
call long
jmp hang
.set COM1, 0x3F8
.macro com1_write offset=0, byte
mov $COM1+\offset, %dx
mov $\byte, %al
outb %al, %dx
.endm
// init_com1: Set up COM1 port for debug output
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
// enable_a20: Allow use of 'high' memory
enable_a20: // TODO more thorough implementation
inb $0x92, %al
or $2, %al
outb %al, $0x92
ret
// unreal: Enter unreal mode
unreal: push %ds
lgdt gdt16_ptr
mov %cr0, %eax
or $0x01, %al
mov %eax, %cr0
ljmp $0x8, $_urprot
_urprot: mov $0x10, %cx
mov %cx, %ds
and $0xFE, %al
mov %eax, %cr0
ljmp $0x0, $_urunreal
_urunreal: pop %ds
ret
// get_map: Retrieve memory map using e820 BIOS function
get_map: mov %ds, %ax
mov %ax, %es
mov $memmap, %di
xor %ebx, %ebx
mov $0x534D4150, %edx // e820 magic number
_gmnext: movl $0, 20(%di)
mov $24, %ecx
mov $0xE820, %eax
int $0x15
jc _gmdone
test %ebx, %ebx
jz _gmdone
add $24, %di
jmp _gmnext
_gmdone: add $24, %di
mov %di, memmap_end
ret
// paging: Set up initial page tables for long mode
paging: mov $4*4096, %ecx
call alloc
mov %eax, pd_ptr
mov $4096, %ecx
call alloc
mov %eax, pdp_ptr
mov $4096, %ecx
call alloc
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)
ret
// make_heap: find a memory range suitable for heap usage
make_heap: mov $memmap-24, %si
_mhnext: add $24, %si
cmp memmap_end, %si
jae _mhdone
cmpl $1, 16(%si)
jne _mhnext
cmpl $0, 4(%si)
ja _mhnext
mov 0(%si), %ebx
mov 8(%si), %ecx
// find end of range, clip to 4Gb
add %ebx, %ecx
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
1: cmp $0x10000, %ebx
jae 1f
mov $0x10000, %ebx
// align to 4Kb boundaries
1: add $0xFFF, %ebx
and $0xFFFFF000, %ebx
and $0xFFFFF000, %ecx
sub %ebx, %ecx
cmp heap_size, %ecx
jbe _mhnext
mov %ebx, heap_start
mov %ecx, heap_size
jmp _mhnext
_mhdone: ret
// alloc: take ECX bytes from heap, return ptr in EAX
// The allocation does not get marked in the memmap.
// No realignment is performed, so only alloc aligned sizes.
alloc: cmp heap_size, %ecx
ja _aerr
mov heap_start, %eax
add %ecx, heap_start
sub %ecx, heap_size
ret
_aerr: mov $msg_aerr, %si
call print
jmp hang
read_file:
.set PXE_TFTP_OPEN, 0x0020
push $1024
push $0x4500
sub $128, %sp
mov $fn_config, %esi
mov %ss, %ax
mov %ax, %es
mov %esp, %edi
mov $fn_config_l, %ecx
rep movsb
push $0
push $0
sub $4, %sp
mov %sp, %di
mov server_ip+0, %al
mov %al, %ss:0(%di)
mov server_ip+1, %al
mov %al, %ss:1(%di)
mov server_ip+2, %al
mov %al, %ss:2(%di)
mov server_ip+3, %al
mov %al, %ss:3(%di)
push $0
pxe_call PXE_TFTP_OPEN
add $14+128, %sp
ret
// print: print NUL-terminated string pointed to by SI
print: xor %bx, %bx
_prnext: mov $COM1+5, %dx
inb %dx, %al
test $0x20, %al
jz _prnext
lodsb
or %al, %al
jz _prdone
mov $COM1, %dx
outb %al, %dx
mov $0x0E, %ah
int $0x10
jmp _prnext
_prdone: ret
// 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
.set IA32_EFER, 0xC0000080
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
// Load long mode GDT, switch to 64-bit CS
lgdt gdt64_ptr
ljmp $0x8, $trampo64
// hang: sleep indefinitely
hang: hlt
jmp hang
_pcerr: mov $msg_pcerr, %si
call print
jmp hang
// gdt16: Protected mode / Unreal mode 16-bit GDT
gdt16: // entry 0: null descriptor
.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
.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
.byte 0x00
.byte 0
.set gdt64_size, .-gdt64
gdt64_ptr: .word gdt64_size-1
.quad gdt64
// Messages to print
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"
msg_mkheap: .asciz " * Making Space\r\n"
msg_paging: .asciz " * Paging\r\n"
msg_read: .asciz " * Retrieving\r\n"
msg_long: .asciz " * Long Mode\r\n"
msg_pserr: .asciz "panic: Missing !PXE structure.\r\n"
msg_pcerr: .asciz "panic: PXE call failed.\r\n"
msg_aerr: .asciz "panic: Out of heap space.\r\n"
fn_config: .asciz "fernlader.cfg"
.set fn_config_l, .-fn_config
pxe_api: .long 0
server_ip: .space 4
heap_start: .long 0
heap_size: .long 0
// 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: .word 0
tx_buf: .space 1024
.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
mov %eax, %ss
jmp loader_main
// ToDo List:
// - Sorting the memmap
// - Sanitizing the memmap
// - Translating the memmap to bootboot format
// - Parsing a config file