# Copyright (C) 2015 The University of Notre Dame
# This software is distributed under the GNU General Public License.
# See the file LICENSE for details.

#include "memorylayout.h"
	
# _start is the initial entry point for the kernel
# Note that we start here merely because it comes
# first in the link order.  The name _start is there
# merely to keep the linker happy.

.code16
.text
.global _start
_start:

# First, jump to the real code start,
# skipping over the immediate data that follows
	jmp	realstart

# At a fixed offset, place an integer that contains
# the length of the kernel in bytes.  This is used
# by the bootblock code to figure out how many sectors to load.
	
.org KERNEL_SIZE_OFFSET
.global kernel_size
kernel_size:
	.long	_end-_start
		
realstart:
	
# While we are briefly still in 16-bit real mode,
# it is safe to call the BIOS to set things up.

# Reset the disk system in order to quiet motors
# and turn off any pending interrupts.
		
	mov	$0,%ah
	int	$0x13

# Turn off the screen cursor, because the
# console will have its own.

	mov	$1,%ah
	mov	$0,%cl
	mov	$0x20,%ch
	int	$0x10

# Get the amount of memory above 1MB and save it for later.
# There are two ways to do this:

# BIOS call 0xe801 measures memory as follows:	
# %ax returns memory above 1MB in 1KB increments, maximum of 16MB.
# %bx returns memory above 64MB in 64KB increments, maximum of 4GB.
# However, this call is relatively new, so if it
# fails, we fall back on memtest2 below.

memtest1:	
	clc
	mov	$0, %bx
	mov	$0xe801,%ax
	int	$0x15
	jc	memtest2
	
	shr	$10, %ax
	shr	$4, %bx
	add	%ax, %bx
	mov	%bx, total_memory-_start
	jmp	memdone

# BIOS call 0x0088 measures memory as follows:
# %ax returns memory above 1MB in 1KB increments, maxiumum of 64MB.

memtest2:
	clc
	mov	$0, %ax
	mov	$0x88, %ah
	int	$0x15
	shr	$10, %ax
	inc	%ax
	mov	%ax, total_memory-_start
memdone:	

# Now, set the video mode using VBE interrupts.
# Keep trying until we find one that works.

# These are documented on page 30 of the VESA-BIOS manual:
# interrupt 0x10
# ax = 0x4f02 "Set VBE Mode"
# bx = mode
#    D0-8   = Mode Number
#    D9-10  = Reserved (must be 0)
#    D11    = 0 Use current default refresh rate.
#    D12-13 = 0 Reserved
#    D14    = 0 Use windowed frame buffer model.
#           = 1 Use linear frame buffer model.
#    D15    = 0 Clear display memory.
#    ES:DI  = Pointer to CRCTCInfoBlock structure.

jmp video640
	
video1280:
 	mov	$0x4f02, %ax
 	mov	$0x411b, %bx
	int	$0x10
	cmp	$0x004f, %ax
	je	videodone
video1024:
 	mov	$0x4f02, %ax
 	mov	$0x4118, %bx
	int	$0x10
	cmp	$0x004f, %ax
	je	videodone
video800:
	mov	$0x4f02, %ax
 	mov	$0x4115, %bx
	int	$0x10
	cmp	$0x004f, %ax
	je	videodone
video640:
	mov	$0x4f02, %ax
 	mov	$0x4112, %bx
	int	$0x10
	cmp	$0x004f, %ax
	je	videodone
video640_lowcolor:
	mov	$0x4f02, %ax
	mov	$0x4111, %bx
	int	$0x10
	cmp	$0x004f, %ax
	je	videodone

videofailed:
	mov	$videomsg, %esi
	call	bios_putstring
	jmp	halt

videodone:	

# After all that, query the video mode and
# figure out the dimensions and the frame
# buffer address.  The set mode is still in bx.

	mov	%ds, %ax		# Set up the extra segment
	mov	%ax, %es		# with the data segment

	mov	$(video_info-_start),%di
	mov	$0x4f01, %ax
	mov	%bx, %cx
	int	$0x10

# In order to use video resolutions higher than 640x480,
# we must enable the A20 address line. The following
# code works on motherboards with "FAST A20", which should
# be everything since the IBM PS/2
inb $0x92, %al
orb $2, %al
outb %al, $0x92
	
# Finally, we are ready to enter protected mode.
# To do this, we disable interrupts so that
# handlers will not see an inconsistent state.
# We then load the new interrupt and descriptor
# tables, which are given below.  Then, we
# enable the protection bit, and load the
# segment selectors into the appropriate registers.
# Finally, we make a long jump to main,
# atomically loading the new code segment and
# starting the kernel.

	cli				# clear interrupts
	lidt	(idt_init-_start)	# load the interrupt table
	lgdt	(gdt_init-_start)	# load the descriptor table
	mov	%cr0, %eax		# get the status word
	or	$0x01, %eax		# turn on the P bit
	mov	%eax, %cr0		# store the status word
					# (we are now in protected mode)
	mov	$2*8, %ax		# selector two is flat 4GB data data
	mov	%ax, %ds		# set data, extra, and stack segments to selector two
	mov	%ax, %es
	mov	%ax, %ss
	mov	$5*8, %ax		# set TSS to selector five
	ltr	%ax
	mov	$0, %ax			# unused segments are nulled out
	mov	%ax, %fs
	mov	%ax, %gs
	mov	$INTERRUPT_STACK_TOP, %sp    # set up initial C stack
	mov	$INTERRUPT_STACK_TOP, %bp    # set up initial C stack
	ljmpl	$(1*8), $(kernel_main)	     # jump to the C main!

# bios_putstring displays an ASCII string pointed to by %si,
# useful for basic startup messages or fatal errors.

bios_putstring:
	mov	(%si), %al
	cmp	$0, %al
	jz	bios_putstring_done
        call	bios_putchar
	inc	%si
	jmp	bios_putstring
bios_putstring_done:
        ret

# bios_putchar invokes the bios to display
# one character on the screen.
	
bios_putchar:
	push	%ax
	push	%bx
        mov	$14,%ah
        mov	$1,%bl
        int	$0x10
	pop	%bx
	pop	%ax
	ret

# The video_info structure is filled in by a BIOS
# call above, and is used to record the basic video
# layout for the graphics subsystem.  See page 30
# of the VBE specification for an explanation of this structure.

.align 4
video_info:
	.word	0
	.byte	0,0
	.word	0,0,0,0
	.long	0
.global video_xbytes
video_xbytes:
	.word	0
.global video_xres
video_xres:
	.word	0 
.global video_yres
video_yres:
	.word	0
	.byte	0,0,0,0,0,0,0,0,0
	.byte	0,0,0,0,0,0,0,0,0
.global video_buffer
video_buffer:
	.long	0
	.long	0
	.word	0
	.word	0
	.byte	0,0,0,0,0,0,0,0,0,0
	.long	0
.rept 190
	.byte	0
.endr

.align 4
videomsg:
	.asciz	"fatal error: couldn't find suitable video mode!\r\n"

###########################
# 32 BIT CODE BEGINS HERE #
###########################

# All code below this point is 32-bit code and data
# that is invoked by higher levels of the kernel from C code.
	
.code32

# Rebooting the machine is easy.
# Set up an invalid interrupt table, and the force an interrupt.
# The machine will triple-fault and reboot itself.

.global reboot
reboot:
	cli
	lidt	idt_invalid	
	int	$1


.global halt
halt:
	cli
	hlt
	jmp	halt

# This is the global descriptor table to be used by the kernel.
# Because we don't really want to use segmentation, we define
# very simple descriptors for global code and data and the TSS

.align 16
.global gdt
gdt:
	.word	0,0,0,0				# seg 0 - null
	.word	0xffff, 0x0000, 0x9a00, 0x00cf	# seg 1 - kernel flat 4GB code
	.word	0xffff, 0x0000, 0x9200, 0x00cf	# seg 2 - kernel flat 4GB data
	.word	0xffff, 0x0000, 0xfa00, 0x00cf	# seg 3 - user flat 4GB code
	.word	0xffff, 0x0000, 0xf200, 0x00cf	# seg 4 - user flat 4GB data
	.word	0x0068, (tss-_start),0x8901, 0x00cf  # seg 5 - TSS
	
# This is the initializer for the global descriptor table.
# It simply tells us the size and location of the table.

gdt_init:
	.word	gdt_init-gdt
	.long	gdt

# The TSS is a big task management structure used by the 386.
# We do not use the TSS, but simply rely on pushing variables
# around in stacks.  However, we need to use the TSS in order
# to initialize the stack pointer and segment for priv level 0

.align 16
.global tss	
tss:
	.long	0
.global interrupt_stack_pointer
interrupt_stack_pointer:	
	.long	INTERRUPT_STACK_TOP # initial interrupt stack ptr at 64 KB
	.long	2*8		    # use segment 2 for the interrupt stack
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0
	.long	0

	
.global total_memory
total_memory:
	.word	32
	
# First, the internal interrupts.
# Note that some already push their own detail
# code onto the stack.  For the others, we push
# a zero, just to get a common layout.

intr00: pushl $0 ; pushl $0  ; jmp intr_handler
intr01: pushl $0 ; pushl $1  ; jmp intr_handler
intr02: pushl $0 ; pushl $2  ; jmp intr_handler
intr03: pushl $0 ; pushl $3  ; jmp intr_handler
intr04: pushl $0 ; pushl $4  ; jmp intr_handler
intr05: pushl $0 ; pushl $5  ; jmp intr_handler
intr06: pushl $0 ; pushl $6  ; jmp intr_handler
intr07: pushl $0 ; pushl $7  ; jmp intr_handler
intr08:            pushl $8  ; jmp intr_handler
intr09: pushl $0 ; pushl $9  ; jmp intr_handler
intr10:            pushl $10 ; jmp intr_handler
intr11:            pushl $11 ; jmp intr_handler
intr12:            pushl $12 ; jmp intr_handler
intr13:            pushl $13 ; jmp intr_handler
intr14:            pushl $14 ; jmp intr_handler
intr15: pushl $0 ; pushl $15 ; jmp intr_handler
intr16: pushl $0 ; pushl $16 ; jmp intr_handler
intr17:            pushl $17 ; jmp intr_handler
intr18: pushl $0 ; pushl $18 ; jmp intr_handler
intr19: pushl $0 ; pushl $19 ; jmp intr_handler

# These interrupts are reserved, but could
# conceivably occur on the next processor model

intr20: pushl $0 ; pushl $20 ; jmp intr_handler
intr21: pushl $0 ; pushl $21 ; jmp intr_handler
intr22: pushl $0 ; pushl $22 ; jmp intr_handler
intr23: pushl $0 ; pushl $23 ; jmp intr_handler
intr24: pushl $0 ; pushl $24 ; jmp intr_handler
intr25: pushl $0 ; pushl $25 ; jmp intr_handler
intr26: pushl $0 ; pushl $26 ; jmp intr_handler
intr27: pushl $0 ; pushl $27 ; jmp intr_handler
intr28: pushl $0 ; pushl $28 ; jmp intr_handler
intr29: pushl $0 ; pushl $29 ; jmp intr_handler
intr30: pushl $0 ; pushl $30 ; jmp intr_handler
intr31: pushl $0 ; pushl $31 ; jmp intr_handler

# Now, the external hardware interrupts.

intr32: pushl $0 ; pushl $32 ; jmp intr_handler
intr33: pushl $0 ; pushl $33 ; jmp intr_handler
intr34: pushl $0 ; pushl $34 ; jmp intr_handler
intr35: pushl $0 ; pushl $35 ; jmp intr_handler
intr36: pushl $0 ; pushl $36 ; jmp intr_handler
intr37: pushl $0 ; pushl $37 ; jmp intr_handler
intr38: pushl $0 ; pushl $38 ; jmp intr_handler
intr39: pushl $0 ; pushl $39 ; jmp intr_handler
intr40: pushl $0 ; pushl $40 ; jmp intr_handler
intr41: pushl $0 ; pushl $41 ; jmp intr_handler
intr42: pushl $0 ; pushl $42 ; jmp intr_handler
intr43: pushl $0 ; pushl $43 ; jmp intr_handler
intr44: pushl $0 ; pushl $44 ; jmp intr_handler
intr45: pushl $0 ; pushl $45 ; jmp intr_handler
intr46: pushl $0 ; pushl $46 ; jmp intr_handler
intr47: pushl $0 ; pushl $47 ; jmp intr_handler
intr48: pushl $0 ; pushl $48 ; jmp intr_syscall

intr_handler:
	pushl	%ds		# push segment registers
	pushl	%es
	pushl	%fs
	pushl	%gs
	pushl	%ebp		# push general regs
	pushl	%edi
	pushl	%esi
	pushl	%edx
	pushl	%ecx
	pushl	%ebx
	pushl	%eax
	pushl	48(%esp)	# push interrupt code from above
	pushl	48(%esp)	# push interrupt number from above
	movl	$2*8, %eax	# switch to kernel data seg and extra seg
	movl	%eax, %ds
	movl	%eax, %es
	call	interrupt_handler
	addl	$4, %esp	# remove interrupt number
	addl	$4, %esp	# remove interrupt code
	jmp	intr_return
	
intr_syscall:
	pushl	%ds		# push segment registers
	pushl	%es
	pushl	%fs
	pushl	%gs
	pushl	%ebp		# push general regs
	pushl	%edi
	pushl	%esi
	pushl	%edx
	pushl	%ecx
	pushl	%ebx
	pushl	%eax		# note these *are* the syscall args
	movl	$2*8, %eax	# switch to kernel data seg and extra seg
	movl	%eax, %ds
	movl	%eax, %es
	call	syscall_handler
	addl	$4, %esp	# remove the old eax
	jmp	syscall_return	

.global intr_return
intr_return:
	popl	%eax
syscall_return:	
	popl	%ebx
	popl	%ecx
	popl	%edx
	popl	%esi
	popl	%edi
	popl	%ebp
	popl	%gs
	popl	%fs
	popl	%es
	popl	%ds
	addl	$4, %esp	# remove interrupt num
	addl	$4, %esp	# remove detail code
	iret			# iret gets the intr context
			
.align 2
idt:
	.word	intr00-_start,1*8,0x8e00,0x0001
	.word	intr01-_start,1*8,0x8e00,0x0001
	.word	intr02-_start,1*8,0x8e00,0x0001
	.word	intr03-_start,1*8,0x8e00,0x0001
	.word	intr04-_start,1*8,0x8e00,0x0001
	.word	intr05-_start,1*8,0x8e00,0x0001
	.word	intr06-_start,1*8,0x8e00,0x0001
	.word	intr07-_start,1*8,0x8e00,0x0001
	.word	intr08-_start,1*8,0x8e00,0x0001
	.word	intr09-_start,1*8,0x8e00,0x0001
	.word	intr10-_start,1*8,0x8e00,0x0001
	.word	intr11-_start,1*8,0x8e00,0x0001
	.word	intr12-_start,1*8,0x8e00,0x0001
	.word	intr13-_start,1*8,0x8e00,0x0001
	.word	intr14-_start,1*8,0x8e00,0x0001
	.word	intr15-_start,1*8,0x8e00,0x0001
	.word	intr16-_start,1*8,0x8e00,0x0001
	.word	intr17-_start,1*8,0x8e00,0x0001
	.word	intr18-_start,1*8,0x8e00,0x0001
	.word	intr19-_start,1*8,0x8e00,0x0001
	.word	intr20-_start,1*8,0x8e00,0x0001
	.word	intr21-_start,1*8,0x8e00,0x0001
	.word	intr22-_start,1*8,0x8e00,0x0001
	.word	intr23-_start,1*8,0x8e00,0x0001
	.word	intr24-_start,1*8,0x8e00,0x0001
	.word	intr25-_start,1*8,0x8e00,0x0001
	.word	intr26-_start,1*8,0x8e00,0x0001
	.word	intr27-_start,1*8,0x8e00,0x0001
	.word	intr28-_start,1*8,0x8e00,0x0001
	.word	intr29-_start,1*8,0x8e00,0x0001
	.word	intr30-_start,1*8,0x8e00,0x0001
	.word	intr31-_start,1*8,0x8e00,0x0001
	.word	intr32-_start,1*8,0x8e00,0x0001
	.word	intr33-_start,1*8,0x8e00,0x0001
	.word	intr34-_start,1*8,0x8e00,0x0001
	.word	intr35-_start,1*8,0x8e00,0x0001
	.word	intr36-_start,1*8,0x8e00,0x0001
	.word	intr37-_start,1*8,0x8e00,0x0001
	.word	intr38-_start,1*8,0x8e00,0x0001
	.word	intr39-_start,1*8,0x8e00,0x0001
	.word	intr40-_start,1*8,0x8e00,0x0001
	.word	intr41-_start,1*8,0x8e00,0x0001
	.word	intr42-_start,1*8,0x8e00,0x0001
	.word	intr43-_start,1*8,0x8e00,0x0001
	.word	intr44-_start,1*8,0x8e00,0x0001
	.word	intr45-_start,1*8,0x8e00,0x0001
	.word	intr46-_start,1*8,0x8e00,0x0001
	.word	intr47-_start,1*8,0x8e00,0x0001
	.word	intr48-_start,1*8,0xee00,0x0001
	
# This is the initializer for the global interrupt table.
# It simply gives the size and location of the interrupt table
	
idt_init:
	.word	idt_init-idt
	.long	idt

# This is the initializer for an invalid interrupt table.
# Its only purpose is to assist with the reboot routine.
	
idt_invalid:
	.word	0
	.long	0