#
! This file contains the assembler startup code for Minix and the 32-bit
! interrupt handlers.  It cooperates with start.c to set up a good
! environment for main().

! This file is part of the lowest layer of the MINIX kernel.  The other part
! is "proc.c".  The lowest layer does process switching and message handling.

! Every transition to the kernel goes through this file.  Transitions are
! caused by sending/receiving messages and by most interrupts.  (RS232
! interrupts may be handled in the file "rs2.s" and then they rarely enter
! the kernel.)

! Transitions to the kernel may be nested.  The initial entry may be with a
! system call, exception or hardware interrupt; reentries may only be made
! by hardware interrupts.  The count of reentries is kept in "k_reenter".
! It is important for deciding whether to switch to the kernel stack and
! for protecting the message passing code in "proc.c".

! For the message passing trap, most of the machine state is saved in the
! proc table.  (Some of the registers need not be saved.)  Then the stack is
! switched to "k_stack", and interrupts are reenabled.  Finally, the system
! call handler (in C) is called.  When it returns, interrupts are disabled
! again and the code falls into the restart routine, to finish off held-up
! interrupts and run the process or task whose pointer is in "proc_ptr".

! Hardware interrupt handlers do the same, except  (1) The entire state must
! be saved.  (2) There are too many handlers to do this inline, so the save
! routine is called.  A few cycles are saved by pushing the address of the
! appropiate restart routine for a return later.  (3) A stack switch is
! avoided when the stack is already switched.  (4) The (master) 8259 interrupt
! controller is reenabled centrally in save().  (5) Each interrupt handler
! masks its interrupt line using the 8259 before enabling (other unmasked)
! interrupts, and unmasks it after servicing the interrupt.  This limits the
! nest level to the number of lines and protects the handler from itself.

! For communication with the boot monitor at startup time some constant
! data are compiled into the beginning of the text segment. This facilitates 
! reading the data at the start of the boot process, since only the first
! sector of the file needs to be read.

! Some data storage is also allocated at the end of this file. This data 
! will be at the start of the data segment of the kernel and will be read
! and modified by the boot monitor before the kernel starts.

! sections

.sect .text
begtext:
.sect .rom
begrom:
.sect .data
begdata:
.sect .bss
begbss:

#include <minix/config.h>
#include <minix/const.h>
#include <minix/com.h>
#include "const.h"
#include "protect.h"
#include "sconst.h"

/* Selected 386 tss offsets. */
#define TSS3_S_SP0	4

! Exported functions
! Note: in assembly language the .define statement applied to a function name 
! is loosely equivalent to a prototype in C code -- it makes it possible to
! link to an entity declared in the assembly code but does not create
! the entity.

.define	_idle_task
.define	_restart
.define	save

.define	_divide_error
.define	_single_step_exception
.define	_nmi
.define	_breakpoint_exception
.define	_overflow
.define	_bounds_check
.define	_inval_opcode
.define	_copr_not_available
.define	_double_fault
.define	_copr_seg_overrun
.define	_inval_tss
.define	_segment_not_present
.define	_stack_exception
.define	_general_protection
.define	_page_fault
.define	_copr_error

.define	_hwint00	! handlers for hardware interrupts
.define	_hwint01
.define	_hwint02
.define	_hwint03
.define	_hwint04
.define	_hwint05
.define	_hwint06
.define	_hwint07
.define	_hwint08
.define	_hwint09
.define	_hwint10
.define	_hwint11
.define	_hwint12
.define	_hwint13
.define	_hwint14
.define	_hwint15

.define	_s_call
.define	_p_s_call
.define	_level0_call

! Imported functions.

.extern	_cstart
.extern	_main
.extern	_exception
.extern	_interrupt
.extern	_sys_call
.extern	_unhold

! Exported variables.
! Note: when used with a variable the .define does not reserve storage,
! it makes the name externally visible so it may be linked to. 

.define	begbss
.define	begdata
.define	_sizes

! Imported variables.

.extern	_gdt
.extern	_code_base
.extern	_data_base
.extern	_held_head
.extern	_k_reenter
.extern	_pc_at
.extern	_proc_ptr
.extern	_ps_mca
.extern	_tss
.extern	_level0_func
.extern _mon_sp
.extern _mon_return
.extern	_reboot_code

.sect .text
!*===========================================================================*
!*				MINIX					     *
!*===========================================================================*
MINIX:				! this is the entry point for the MINIX kernel
	jmp	over_flags	! skip over the next few bytes
	.data2	CLICK_SHIFT	! for the monitor: memory granularity
flags:
	.data2	0x002D		! boot monitor flags:
				!	call in 386 mode, make stack,
				!	load high, will return
	nop			! extra byte to sync up disassembler
over_flags:

! Set up a C stack frame on the monitor stack.  (The monitor sets cs and ds
! right.  The ss descriptor still references the monitor data segment.)
	movzx	esp, sp		! monitor stack is a 16 bit stack
	push	ebp
	mov	ebp, esp
	push	esi
	push	edi
	cmp	4(ebp), 0	! nonzero if return possible
	jz	noret
	inc	(_mon_return)
noret:	mov	(_mon_sp), esp	! save stack pointer for later return

! Copy the monitor global descriptor table to the address space of kernel and
! switch over to it.  Prot_init() can then update it with immediate effect.

	sgdt	(_gdt+GDT_SELECTOR)		! get the monitor gdtr
	mov	esi, (_gdt+GDT_SELECTOR+2)	! absolute address of GDT
	mov	ebx, _gdt			! address of kernel GDT
	mov	ecx, 8*8			! copying eight descriptors
copygdt:
 eseg	movb	al, (esi)
	movb	(ebx), al
	inc	esi
	inc	ebx
	loop	copygdt
	mov	eax, (_gdt+DS_SELECTOR+2)	! base of kernel data
	and	eax, 0x00FFFFFF			! only 24 bits
	add	eax, _gdt			! eax = vir2phys(gdt)
	mov	(_gdt+GDT_SELECTOR+2), eax	! set base of GDT
	lgdt	(_gdt+GDT_SELECTOR)		! switch over to kernel GDT

! Locate boot parameters, set up kernel segment registers and stack.
	mov	ebx, 8(ebp)	! boot parameters offset
	mov	edx, 12(ebp)	! boot parameters length
	mov	ax, ds		! kernel data
	mov	es, ax
	mov	fs, ax
	mov	gs, ax
	mov	ss, ax
	mov	esp, k_stktop	! set sp to point to the top of kernel stack

! Call C startup code to set up a proper environment to run main().
	push	edx
	push	ebx
	push	SS_SELECTOR
	push	MON_CS_SELECTOR
	push	DS_SELECTOR
	push	CS_SELECTOR
	call	_cstart		! cstart(cs, ds, mcs, mds, parmoff, parmlen)
	add	esp, 6*4

! Reload gdtr, idtr and the segment registers to global descriptor table set
! up by prot_init().

	lgdt	(_gdt+GDT_SELECTOR)
	lidt	(_gdt+IDT_SELECTOR)

	jmpf	CS_SELECTOR:csinit
csinit:
    o16	mov	ax, DS_SELECTOR
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	gs, ax
	mov	ss, ax
    o16	mov	ax, TSS_SELECTOR	! no other TSS is used
	ltr	ax
	push	0			! set flags to known good state
	popf				! esp, clear nested task and int enable

	jmp	_main			! main()


!*===========================================================================*
!*				interrupt handlers			     *
!*		interrupt handlers for 386 32-bit protected mode	     *
!*===========================================================================*

!*===========================================================================*
!*				hwint00 - 07				     *
!*===========================================================================*
! Note this is a macro, it looks like a subroutine.
#define hwint_master(irq)	\
	call	save			/* save interrupted process state */;\
	inb	INT_CTLMASK						    ;\
	orb	al, [1<<irq]						    ;\
	outb	INT_CTLMASK		/* disable the irq		  */;\
	movb	al, ENABLE						    ;\
	outb	INT_CTL			/* reenable master 8259		  */;\
	sti				/* enable interrupts		  */;\
	push	irq			/* irq				  */;\
	call	(_irq_table + 4*irq)	/* eax = (*irq_table[irq])(irq)   */;\
	pop	ecx							    ;\
	cli				/* disable interrupts		  */;\
	test	eax, eax		/* need to reenable irq?	  */;\
	jz	0f							    ;\
	inb	INT_CTLMASK						    ;\
	andb	al, ~[1<<irq]						    ;\
	outb	INT_CTLMASK		/* enable the irq		  */;\
0:	ret				/* restart (another) process      */

! Each of these entry points is an expansion of the hwint_master macro
	.align	16
_hwint00:		! Interrupt routine for irq 0 (the clock).
	hwint_master(0)

	.align	16
_hwint01:		! Interrupt routine for irq 1 (keyboard)
	hwint_master(1)

	.align	16
_hwint02:		! Interrupt routine for irq 2 (cascade!)
	hwint_master(2)

	.align	16
_hwint03:		! Interrupt routine for irq 3 (second serial)
	hwint_master(3)

	.align	16
_hwint04:		! Interrupt routine for irq 4 (first serial)
	hwint_master(4)

	.align	16
_hwint05:		! Interrupt routine for irq 5 (XT winchester)
	hwint_master(5)

	.align	16
_hwint06:		! Interrupt routine for irq 6 (floppy)
	hwint_master(6)

	.align	16
_hwint07:		! Interrupt routine for irq 7 (printer)
	hwint_master(7)

!*===========================================================================*
!*				hwint08 - 15				     *
!*===========================================================================*
! Note this is a macro, it looks like a subroutine.
#define hwint_slave(irq)	\
	call	save			/* save interrupted process state */;\
	inb	INT2_CTLMASK						    ;\
	orb	al, [1<<[irq-8]]					    ;\
	outb	INT2_CTLMASK		/* disable the irq		  */;\
	movb	al, ENABLE						    ;\
	outb	INT_CTL			/* reenable master 8259		  */;\
	jmp	.+2			/* delay			  */;\
	outb	INT2_CTL		/* reenable slave 8259		  */;\
	sti				/* enable interrupts		  */;\
	push	irq			/* irq				  */;\
	call	(_irq_table + 4*irq)	/* eax = (*irq_table[irq])(irq)   */;\
	pop	ecx							    ;\
	cli				/* disable interrupts		  */;\
	test	eax, eax		/* need to reenable irq?	  */;\
	jz	0f							    ;\
	inb	INT2_CTLMASK						    ;\
	andb	al, ~[1<<[irq-8]]					    ;\
	outb	INT2_CTLMASK		/* enable the irq		  */;\
0:	ret				/* restart (another) process      */

! Each of these entry points is an expansion of the hwint_slave macro
	.align	16
_hwint08:		! Interrupt routine for irq 8 (realtime clock)
	hwint_slave(8)

	.align	16
_hwint09:		! Interrupt routine for irq 9 (irq 2 redirected)
	hwint_slave(9)

	.align	16
_hwint10:		! Interrupt routine for irq 10
	hwint_slave(10)

	.align	16
_hwint11:		! Interrupt routine for irq 11
	hwint_slave(11)

	.align	16
_hwint12:		! Interrupt routine for irq 12
	hwint_slave(12)

	.align	16
_hwint13:		! Interrupt routine for irq 13 (FPU exception)
	hwint_slave(13)

	.align	16
_hwint14:		! Interrupt routine for irq 14 (AT winchester)
	hwint_slave(14)

	.align	16
_hwint15:		! Interrupt routine for irq 15
	hwint_slave(15)

!*===========================================================================*
!*				save					     *
!*===========================================================================*
! Save for protected mode.
! This is much simpler than for 8086 mode, because the stack already points
! into the process table, or has already been switched to the kernel stack.

	.align	16
save:
	cld			! set direction flag to a known value
	pushad			! save "general" registers
    o16	push	ds		! save ds
    o16	push	es		! save es
    o16	push	fs		! save fs
    o16	push	gs		! save gs
	mov	dx, ss		! ss is kernel data segment
	mov	ds, dx		! load rest of kernel segments
	mov	es, dx		! kernel does not use fs, gs
	mov	eax, esp	! prepare to return
	incb	(_k_reenter)	! from -1 if not reentering
	jnz	set_restart1	! stack is already kernel stack
	mov	esp, k_stktop
	push	_restart	! build return address for int handler
	xor	ebp, ebp	! for stacktrace
	jmp	RETADR-P_STACKBASE(eax)

	.align	4
set_restart1:
	push	restart1
	jmp	RETADR-P_STACKBASE(eax)

!*===========================================================================*
!*				_s_call					     *
!*===========================================================================*
	.align	16
_s_call:
_p_s_call:
	cld			! set direction flag to a known value
	sub	esp, 6*4	! skip RETADR, eax, ecx, edx, ebx, est
	push	ebp		! stack already points into proc table
	push	esi
	push	edi
    o16	push	ds
    o16	push	es
    o16	push	fs
    o16	push	gs
	mov	dx, ss
	mov	ds, dx
	mov	es, dx
	incb	(_k_reenter)
	mov	esi, esp	! assumes P_STACKBASE == 0
	mov	esp, k_stktop
	xor	ebp, ebp	! for stacktrace
				! end of inline save
	sti			! allow SWITCHER to be interrupted
				! now set up parameters for sys_call()
	push	ebx		! pointer to user message
	push	eax		! src/dest
	push	ecx		! SEND/RECEIVE/BOTH
	call	_sys_call	! sys_call(function, src_dest, m_ptr)
				! caller is now explicitly in proc_ptr
	mov	AXREG(esi), eax	! sys_call MUST PRESERVE si
	cli			! disable interrupts 

! Fall into code to restart proc/task running.

!*===========================================================================*
!*				restart					     *
!*===========================================================================*
_restart:

! Flush any held-up interrupts.
! This reenables interrupts, so the current interrupt handler may reenter.
! This does not matter, because the current handler is about to exit and no
! other handlers can reenter since flushing is only done when k_reenter == 0.

	cmp	(_held_head), 0	! do fast test to usually avoid function call
	jz	over_call_unhold
	call	_unhold		! this is rare so overhead acceptable
over_call_unhold:
	mov	esp, (_proc_ptr)	! will assume P_STACKBASE == 0
	lldt	P_LDT_SEL(esp)		! enable segment descriptors for task
	lea	eax, P_STACKTOP(esp)	! arrange for next interrupt
	mov	(_tss+TSS3_S_SP0), eax	! to save state in process table
restart1:
	decb	(_k_reenter)
    o16	pop	gs
    o16	pop	fs
    o16	pop	es
    o16	pop	ds
	popad
	add	esp, 4		! skip return adr
	iretd			! continue process

!*===========================================================================*
!*				exception handlers			     *
!*===========================================================================*
_divide_error:
	push	DIVIDE_VECTOR
	jmp	exception

_single_step_exception:
	push	DEBUG_VECTOR
	jmp	exception

_nmi:
	push	NMI_VECTOR
	jmp	exception

_breakpoint_exception:
	push	BREAKPOINT_VECTOR
	jmp	exception

_overflow:
	push	OVERFLOW_VECTOR
	jmp	exception

_bounds_check:
	push	BOUNDS_VECTOR
	jmp	exception

_inval_opcode:
	push	INVAL_OP_VECTOR
	jmp	exception

_copr_not_available:
	push	COPROC_NOT_VECTOR
	jmp	exception

_double_fault:
	push	DOUBLE_FAULT_VECTOR
	jmp	errexception

_copr_seg_overrun:
	push	COPROC_SEG_VECTOR
	jmp	exception

_inval_tss:
	push	INVAL_TSS_VECTOR
	jmp	errexception

_segment_not_present:
	push	SEG_NOT_VECTOR
	jmp	errexception

_stack_exception:
	push	STACK_FAULT_VECTOR
	jmp	errexception

_general_protection:
	push	PROTECTION_VECTOR
	jmp	errexception

_page_fault:
	push	PAGE_FAULT_VECTOR
	jmp	errexception

_copr_error:
	push	COPROC_ERR_VECTOR
	jmp	exception

!*===========================================================================*
!*				exception				     *
!*===========================================================================*
! This is called for all exceptions which do not push an error code.

	.align	16
exception:
 sseg	mov	(trap_errno), 0		! clear trap_errno
 sseg	pop	(ex_number)
	jmp	exception1

!*===========================================================================*
!*				errexception				     *
!*===========================================================================*
! This is called for all exceptions which push an error code.

	.align	16
errexception:
 sseg	pop	(ex_number)
 sseg	pop	(trap_errno)
exception1:				! Common for all exceptions.
	push	eax			! eax is scratch register
	mov	eax, 0+4(esp)		! old eip
 sseg	mov	(old_eip), eax
	movzx	eax, 4+4(esp)		! old cs
 sseg	mov	(old_cs), eax
	mov	eax, 8+4(esp)		! old eflags
 sseg	mov	(old_eflags), eax
	pop	eax
	call	save
	push	(old_eflags)
	push	(old_cs)
	push	(old_eip)
	push	(trap_errno)
	push	(ex_number)
	call	_exception		! (ex_number, trap_errno, old_eip,
					!	old_cs, old_eflags)
	add	esp, 5*4
	cli
	ret

!*===========================================================================*
!*				level0_call				     *
!*===========================================================================*
_level0_call:
	call	save
	jmp	(_level0_func)

!*===========================================================================*
!*				idle_task				     *
!*===========================================================================*
_idle_task:			! executed when there is no work
	jmp	_idle_task	! a "hlt" before this fails in protected mode

!*===========================================================================*
!*				data					     *
!*===========================================================================*
! These declarations assure that storage will be allocated at the very 
! beginning of the kernel data section, so the boot monitor can be easily 
! told how to patch these locations. Note that the magic number is put
! here by the compiler, but will be read by, and then overwritten by,
! the boot monitor. When the kernel starts the sizes array will be
! found here, as if it had been initialized by the compiler.

.sect .rom	! Before the string table please
_sizes:				! sizes of kernel, mm, fs filled in by boot
	.data2	0x526F		! this must be the first data entry (magic #)
	.space	16*2*2-2	! monitor uses previous word and this space
				! extra space allows for additional servers
.sect .bss
k_stack:
	.space	K_STACK_BYTES	! kernel stack
k_stktop:			! top of kernel stack
	.comm	ex_number, 4
	.comm	trap_errno, 4
	.comm	old_eip, 4
	.comm	old_cs, 4
	.comm	old_eflags, 4
