.code16
.section .stage2

# kernel framebuffer information format
#	.align 8
#	.long 0xBABAB007
#	.long -(0xBABAB007 + width + height + bpp)
#	.long width		(2 bytes used, 4 bytes for ease of calculation)
#	.long height	(2 bytes used, 4 bytes for ease of calculation)
#	.long bpp		(1 bytes used, 4 bytes for ease of calculation)

# scan memory 0x100000 -> 0x200000 for framebuffer information
# return:
#	ax: target width
#	bx: target height
#	cx: target bpp
vesa_scan_kernel_image:
	pushl %edx
	pushl %esi

	movl $0x100000, %esi

 .vesa_scan_kernel_image_loop:
	# check magic
	cmpl $0xBABAB007, (%esi)
	jne .vesa_scan_kernel_image_next_addr

	# check checksum
	movl 0x00(%esi), %edx
	addl 0x04(%esi), %edx
	addl 0x08(%esi), %edx
	addl 0x0C(%esi), %edx
	addl 0x10(%esi), %edx
	testl %edx, %edx
	jnz .vesa_scan_kernel_image_next_addr

	# set return registers
	movw 0x08(%esi), %ax
	movw 0x0C(%esi), %bx
	movw 0x10(%esi), %cx
	jmp .vesa_scan_kernel_image_done

 .vesa_scan_kernel_image_next_addr:
	addl $8, %esi
	cmpl $0x200000, %esi
	jb .vesa_scan_kernel_image_loop

	# zero out return registers
	xorw %ax, %ax
	xorw %bx, %bx
	xorw %cx, %cx

 .vesa_scan_kernel_image_done:
	popl %esi
	popl %edx
	ret

# Find suitable video mode and save it in (vesa_target_mode)
vesa_find_video_mode:
	pushal

	pushl %ebp
	movl %esp, %ebp
	subl $6, %esp

	# clear target mode and frame buffer
	movw $0, (vesa_target_mode)
	movl $0, (framebuffer + 0)
	movl $0, (framebuffer + 4)
	movl $0, (framebuffer + 8)
	movl $0, (framebuffer + 12)
	movw $0, (framebuffer + 16)

	call vesa_scan_kernel_image
	testw %ax, %ax
	jz .vesa_find_video_mode_loop_modes_done

	# save arguments in stack
	movw %ax, -2(%ebp)
	movw %bx, -4(%ebp)
	movw %cx, -6(%ebp)

	# get vesa information
	movw $0x4F00, %ax
	movw $vesa_info_buffer, %di
	pushl %ebp; int $0x10; popl %ebp	# BOCHS doesn't seem to reserve ebp
	cmpb $0x4F, %al; jne .vesa_unsupported
	cmpb $0x00, %ah; jne .vesa_error

	# confirm that response starts with 'VESA'
	cmpl $0x41534556, (vesa_info_buffer)
	jne .vesa_error

	# confirm that version is atleast 2.0
	cmpw $0x0200, (vesa_info_buffer + 0x04)
	jb .vesa_unsupported_version

	movl (vesa_info_buffer + 0x0E), %esi
 .vesa_find_video_mode_loop_modes:
	cmpw $0xFFFF, (%esi)
	je .vesa_find_video_mode_loop_modes_done

	# get info of next mode
	movw $0x4F01, %ax
	movw (%esi), %cx
	movw $vesa_mode_info_buffer, %di
	pushl %ebp; int $0x10; popl %ebp	# BOCHS doesn't seem to reserve ebp
	cmpb $0x4F, %al; jne .vesa_unsupported
	cmpb $0x00, %ah; jne .vesa_error

	# check whether in graphics mode
	testb $0x10, (vesa_mode_info_buffer + 0)
	jz .vesa_find_video_mode_next_mode

	# compare mode's dimensions
	movw -2(%ebp), %ax; cmpw %ax, (vesa_mode_info_buffer + 0x12)
	jne .vesa_find_video_mode_next_mode
	movw -4(%ebp), %ax; cmpw %ax, (vesa_mode_info_buffer + 0x14)
	jne .vesa_find_video_mode_next_mode
	movb -6(%ebp), %al; cmpb %al, (vesa_mode_info_buffer + 0x19)
	jne .vesa_find_video_mode_next_mode

	# set address, pitch, type
	movl (vesa_mode_info_buffer + 0x28), %esi
	movl %esi, (framebuffer + 0)
	movw (vesa_mode_info_buffer + 0x10), %ax
	movw %ax,  (framebuffer + 4)
	movb $1,   (framebuffer + 17)

	# set width, height, bpp
	movw -2(%ebp), %ax; movw %ax, (framebuffer +  8)
	movw -4(%ebp), %ax; movw %ax, (framebuffer + 12)
	movw -6(%ebp), %ax; movb %al, (framebuffer + 16)

	movw %cx, (vesa_target_mode)
	jmp .vesa_find_video_mode_loop_modes_done

 .vesa_find_video_mode_next_mode:
	addl $2, %esi
	jmp .vesa_find_video_mode_loop_modes

 .vesa_find_video_mode_loop_modes_done:
	leavel
	popal
	ret

 .vesa_unsupported:
	movw $vesa_unsupported_msg, %si
	jmp print_and_halt
 .vesa_unsupported_version:
	movw $vesa_unsupported_version_msg, %si
	jmp print_and_halt
 .vesa_error:
	movw $vesa_error_msg, %si
	jmp print_and_halt


# scan for video mode in kernel memory and set the correct one.
# when video mode is not found or does not exists,
# set it to 80x25 text mode to clear the screen.
.global vesa_set_video_mode
vesa_set_video_mode:
	pushw %ax
	pushw %bx

	call vesa_find_video_mode

	movw (vesa_target_mode), %bx
	testw %bx, %bx
	jz .vesa_set_target_mode_generic

	movw $0x4F02, %ax
	orw $0x4000, %bx
	pushl %ebp; int $0x10; popl %ebp	# BOCHS doesn't seem to reserve ebp

	jmp .set_video_done

 .vesa_set_target_mode_generic:
	movb $0x03, %al
	movb $0x00, %ah
	pushl %ebp; int $0x10; popl %ebp	# BOCHS doesn't seem to reserve ebp

 .set_video_done:
	popw %bx
	popw %ax
	ret

.section .data

vesa_error_msg:
	.asciz "VESA error"
vesa_unsupported_msg:
	.asciz "VESA unsupported"
vesa_unsupported_version_msg:
	.asciz "VESA unsupported version"
vesa_success_msg:
	.asciz "VESA success"

.section .bss

vesa_info_buffer:
	.skip 512

vesa_mode_info_buffer:
	.skip 256

vesa_target_mode:
	.skip 2

.global framebuffer
.align 8
framebuffer:
	.skip 4	# address
	.skip 4	# pitch
	.skip 4	# width
	.skip 4 # height
	.skip 1 # bpp
	.skip 1 # type