.set SECTOR_SIZE,	512

# file header field offsets
.set e_type,		16
.set e_machine,		18
.set e_version,		20
.set e_entry,		24

.set e32_phoff,		28
.set e32_shoff,		32
.set e32_flags,		36
.set e32_ehsize,	40
.set e32_phentsize,	42
.set e32_phnum,		44
.set e32_shentsize,	46
.set e32_shnum,		48
.set e32_shstrndx,	50

.set e64_phoff,		32
.set e64_shoff,		40
.set e64_flags,		48
.set e64_ehsize,	52
.set e64_phentsize,	54
.set e64_phnum,		56
.set e64_shentsize,	58
.set e64_shnum,		60
.set e64_shstrndx,	62

# e_ident offsets
.set EI_CLASS,		4
.set EI_DATA,		5
.set EI_VERSION,	6

# e_ident constants
.set ELFMAGIC,		0x464C457F
.set ELFCLASS32,	1
.set ELFCLASS64,	2
.set ELFDATA2LSB,	1
.set EV_CURRENT,	1

# e_type constants
.set ET_EXEC,		2

# program header field offsets
.set p_type,		0

.set p32_offset,	4
.set p32_vaddr,		8
.set p32_paddr,		12
.set p32_filesz,	16
.set p32_memsz,		20
.set p32_flags,		24
.set p32_align,		28

.set p64_flags,		4
.set p64_offset,	8
.set p64_vaddr,		16
.set p64_paddr,		24
.set p64_filesz,	32
.set p64_memsz,		40
.set p64_align,		48

# p_type constants
.set PT_NULL,		0
.set PT_LOAD,		1

# mask for entry point and segment loading
.set LOAD_MASK,		0x07FFFFFF

.code16
.section .stage2

# Validate file header stored in elf_file_header
# returns only on success
elf_validate_file_header:
	cmpl $ELFMAGIC, (elf_file_header)
	jne .elf_validate_file_header_invalid_magic

	cmpb $ELFCLASS32, (elf_file_header + EI_CLASS)
	je .elf_validate_file_header_class_valid
	cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
	je .elf_validate_file_header_class_valid
	jmp .elf_validate_file_header_invalid_class
 .elf_validate_file_header_class_valid:

	cmpb $ELFDATA2LSB, (elf_file_header + EI_DATA)
	jne .elf_validate_file_header_only_little_endian_supported

	cmpb $EV_CURRENT, (elf_file_header + EI_VERSION)
	jne .elf_validate_file_header_not_current_version

	cmpl $EV_CURRENT, (elf_file_header + e_version)
	jne .elf_validate_file_header_not_current_version

	cmpw $ET_EXEC, (elf_file_header + e_type)
	jne .elf_validate_file_header_not_executable

	ret

 .elf_validate_file_header_invalid_magic:
	movw $elf_validate_file_header_invalid_magic_msg, %si
	jmp print_and_halt
 .elf_validate_file_header_invalid_class:
	movw $elf_validate_file_header_invalid_class_msg, %si
	jmp print_and_halt
 .elf_validate_file_header_only_little_endian_supported:
	movw $elf_validate_file_header_only_little_endian_supported_msg, %si
	jmp print_and_halt
 .elf_validate_file_header_not_current_version:
	movw $elf_validate_file_header_not_current_version_msg, %si
	jmp print_and_halt
 .elf_validate_file_header_not_executable:
	movw $elf_validate_file_header_not_executable_msg, %si
	jmp print_and_halt

# reads memory specified by 32 bit elf_program_header to memory
elf_read_program_header32_to_memory:
	pushal
	pushl %ebp
	movl %esp, %ebp

	# memset p_filesz -> p_memsz to 0
	movl (elf_program_header + p32_filesz), %ebx
	movl (elf_program_header + p32_vaddr),  %edi
	andl $LOAD_MASK, %edi
	addl %ebx, %edi
	movl (elf_program_header + p32_memsz),  %ecx
	subl %ebx, %ecx
	xorb %al, %al; call memset32

	# read file specified in program header to memory
	movl (elf_program_header + p32_offset), %eax
	movl (elf_program_header + p32_vaddr),  %edi
	andl $LOAD_MASK, %edi
	movl (elf_program_header + p32_filesz), %ecx
	call *%esi

	leavel
	popal
	ret


# reads memory specified by 64 bit elf_program_header to memory
elf_read_program_header64_to_memory:
	pushal
	pushl %ebp
	movl %esp, %ebp

	# memset p_filesz -> p_memsz to 0
	movl (elf_program_header + p64_filesz), %ebx
	movl (elf_program_header + p64_vaddr),  %edi
	andl $LOAD_MASK, %edi
	addl %ebx, %edi
	movl (elf_program_header + p64_memsz),  %ecx
	subl %ebx, %ecx
	xorb %al, %al; call memset32

	# read file specified in program header to memory
	movl (elf_program_header + p64_offset), %eax
	movl (elf_program_header + p64_vaddr),  %edi
	andl $LOAD_MASK, %edi
	movl (elf_program_header + p64_filesz), %ecx
	call *%esi

	leavel
	popal
	ret


# read callback format
#	eax: first byte
#	ecx: byte count
#	edi: buffer
# returns only on success


# reads kernel to memory
#	esi:	callback for reading from kernel image
# return:
#	eax:	kernel entry address
.global elf_read_kernel_to_memory
elf_read_kernel_to_memory:
	pushal
	pushl %ebp
	movl %esp, %ebp
	subl $2, %esp

	# read start of file header
	movl $0, %eax
	movl $24, %ecx
	movl $elf_file_header, %edi
	call *%esi

	call elf_validate_file_header

	# determine file header size
	movl $52, %ecx
	movl $64, %edx
	cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
	cmovel %edx, %ecx

	# read full file header
	movl $0, %eax
	movl $elf_file_header, %edi
	call *%esi

	# verify that e_phoff fits in 32 bits
	cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
	jne .elf_read_kernel_to_memory_valid_offset
	cmpl $0, (elf_file_header + e64_phoff + 4)
	jnz .elf_read_kernel_to_memory_unsupported_offset
 .elf_read_kernel_to_memory_valid_offset:

	# read architecture phentsize and phnum to fixed locations
	movw (elf_file_header + e32_phentsize), %ax
	movw (elf_file_header + e32_phnum),     %bx
	movl (elf_file_header + e32_phoff),     %ecx
	cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
	cmovew (elf_file_header + e64_phentsize), %ax
	cmovew (elf_file_header + e64_phnum),     %bx
	cmovel (elf_file_header + e64_phoff),     %ecx
	movw %ax,  (elf_file_header_phentsize)
	movw %bx,  (elf_file_header_phnum)
	movl %ecx, (elf_file_header_phoff)

	# current program header
	movw $0, -2(%ebp)

 .elf_read_kernel_to_memory_loop_program_headers:
	movw -2(%ebp), %cx
	cmpw (elf_file_header_phnum), %cx
	jae .elf_read_kernel_to_memory_done

	# eax := program_header_index * e_phentsize + e_phoff
	xorl %eax, %eax
	movw %cx, %ax
	xorl %ebx, %ebx
	movw (elf_file_header_phentsize), %bx
	mull %ebx
	addl (elf_file_header_phoff), %eax
	jc .elf_read_kernel_to_memory_unsupported_offset

	# determine program header size
	movl $32, %ecx
	movl $56, %edx
	cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
	cmovel %edx, %ecx

	# read program header
	movl $elf_program_header, %edi
	call *%esi

	# test if program header is NULL header
	cmpl $PT_NULL, (elf_program_header + p_type)
	je .elf_read_kernel_to_memory_null_program_header

	# confirm that the program header is loadable
	cmpl $PT_LOAD, (elf_program_header + p_type)
	jne .elf_read_kernel_to_memory_not_loadable_header

	# read program header to memory
	movl $elf_read_program_header32_to_memory, %eax
	movl $elf_read_program_header64_to_memory, %ebx
	cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
	cmovel %ebx, %eax
	call *%eax

 .elf_read_kernel_to_memory_null_program_header:
	incw -2(%ebp)
	jmp .elf_read_kernel_to_memory_loop_program_headers

 .elf_read_kernel_to_memory_done:
	leavel
	popal

	# set kernel entry address
	movl (elf_file_header + e_entry), %eax
	andl $LOAD_MASK, %eax

	ret

 .elf_read_kernel_to_memory_unsupported_offset:
	movw $elf_read_kernel_to_memory_unsupported_offset_msg, %si
	jmp print_and_halt
 .elf_read_kernel_to_memory_not_loadable_header:
	movw $elf_read_kernel_to_memory_not_loadable_header_msg, %si
	jmp print_and_halt

.section .data

elf_validate_file_header_invalid_magic_msg:
	.asciz "ELF: file has invalid ELF magic"
elf_validate_file_header_invalid_class_msg:
	.asciz "ELF: file has invalid ELF class"
elf_validate_file_header_only_little_endian_supported_msg:
	.asciz "ELF: file is not in little endian format"
elf_validate_file_header_not_current_version_msg:
	.asciz "ELF: file is not in current ELF version"
elf_validate_file_header_not_executable_msg:
	.asciz "ELF: file is not an executable"

elf_read_kernel_to_memory_unsupported_offset_msg:
	.asciz "ELF: unsupported offset (only 32 bit offsets supported)"
elf_read_kernel_to_memory_not_loadable_header_msg:
	.asciz "ELF: kernel contains non-loadable program header"

.section .bss

elf_file_header:
	.skip 64

elf_file_header_phentsize:
	.skip 2
elf_file_header_phnum:
	.skip 2
elf_file_header_phoff:
	.skip 4 # NOTE: only 32 bit offsets are supported

elf_program_header:
	.skip 56