!	Boothead.s - BIOS support for boot.c		Author: Kees J. Bot
!
!
! This file contains the startup and low level support for the secondary
! boot program.  It contains functions for disk, tty and keyboard I/O,
! copying memory to arbitrary locations, etc.
!
! The primary bootstrap code supplies the following parameters in registers:
!	dl	= Boot-device.
!	es:si	= Partition table entry if hard disk.
!

.define begtext, begdata, begbss
.data
begdata:
	.ascii	"(null)\0"
.bss
begbss:

	BOOTOFF	   =	0x7C00	! 0x0000:BOOTOFF load a bootstrap here
	LOADSEG    =	0x1000	! Where this code is loaded.
	BUFFER	   =	0x0600	! First free memory
	PENTRYSIZE =	    16	! Partition table entry size.
	DSKBASE    =	   120	! 120 = 4 * 0x1E = ptr to disk parameters
	DSKPARSIZE =	    11	! 11 bytes of floppy parameters
	SECTORS	   =	     4	! Offset into parameters to sectors per track
	a_flags	   =	     2	! From a.out.h, struct exec
	a_text	   =	     8
	a_data	   =	    12
	a_bss	   =	    16
	a_total	   =	    24
	A_SEP	   =	  0x20	! Separate I&D flag.

! Imported variables and functions:
.extern _cseg, _dseg, _runsize, _edata, _end	! Runtime environment
.extern _device, _dskpars, _heads, _sectors	! Boot disk parameters
.extern _eqscancode				! Ugly scancode trick
.extern _rem_part				! To pass partition info

.text
begtext:
.extern _boot, _printk				! Boot Minix, kernel printf

! Set segment registers and stack pointer using the programs own header!
! The header is either 32 bytes (short form) or 48 bytes (long form).  The
! bootblock will jump to address 0x10030 in both cases, calling one of the
! two jmpf instructions below.

	jmpf	boot, LOADSEG+3	! Set cs right (skipping long a.out header)
	.space	11		! jmpf + 11 = 16 bytes
	jmpf	boot, LOADSEG+2	! Set cs right (skipping short a.out header)
boot:
	mov	ax, #LOADSEG
	mov	ds, ax		! ds = header

	movb	al, a_flags
	testb	al, #A_SEP	! Separate I&D?
	jnz	sepID
comID:	xor	ax, ax
	xchg	ax, a_text	! No text
	add	a_data, ax	! Treat all text as data
sepID:
	mov	ax, a_total	! Total nontext memory usage
	and	ax, #0xFFFE	! Round down to even
	mov	a_total, ax	! total - text = data + bss + heap + stack
	cli			! Ignore interrupts while stack in limbo
	mov	sp, ax		! Set sp at the top of all that

	mov	ax, a_text	! Determine offset of ds above cs
	movb	cl, #4
	shr	ax, cl
	mov	cx, cs
	add	ax, cx
	mov	ds, ax		! ds = cs + text / 16
	mov	ss, ax
	sti			! Stack ok now
	push	es		! Save es, we need it for the partition table
	mov	es, ax
	cld			! C compiler wants UP

! Clear bss
	xor	ax, ax		! Zero
	mov	di, #_edata	! Start of bss is at end of data
	mov	cx, #_end	! End of bss (begin of heap)
	sub	cx, di		! Number of bss bytes
	shr	cx, #1		! Number of words
	rep
	stos			! Clear bss

! Copy primary boot parameters to variables.  (Can do this now that bss is
! cleared and may be written into).
	xorb	dh, dh
	mov	_device, dx	! Boot device (probably 0x00 or 0x80)
	mov	_rem_part+0, si	! Remote partition table offset
	pop	_rem_part+2	! and segment (saved es)

! Give C code access to the code segment, data segment and the size of this
! process.
	mov	_cseg, cs
	mov	_dseg, ds
	push	ds
	mov	ax, #LOADSEG
	mov	ds, ax		! Back to the header once more
	mov	ax, a_total+0
	mov	dx, a_total+2	! dx:ax = data + bss + heap + stack
	add	ax, a_text+0
	adc	dx, a_text+2	! dx:ax = text + data + bss + heap + stack
	pop	ds
	mov	_runsize+0, ax
	mov	_runsize+2, dx	! 32 bit size of this process

! Time to switch to a higher level language (not much higher)
	call	_boot

.define	_exit, __exit, _main	! Make various compilers happy
.define _reboot			! Normal reboot entry point
_exit:
__exit:
_main:
_reboot:
	mov	ax, #any_key
	push	ax
	call	_printk
	call	_getchar
	int	0x19
.data
any_key:
	.ascii	"\nHit any key to reboot\n\0"
.text

! Alas we need some low level support
!
! void raw_copy(dstoff, dstseg, srcoff, srcseg, count)
!	Copy count bytes from srcseg:srcoff to dstseg:dstoff
!	Don't do overlaps.  Count is 16 bits.
.define _raw_copy
_raw_copy:
	mov	bx, sp
	push	si
	push	di		! Save C variable registers
	mov	cx, 10(bx)	! Count (get it before ds is trashed)
	les	di, 2(bx)	! es:di = dstseg:dstoff
	lds	si, 6(bx)	! ds:si = srcseg:srcoff
	shr	cx, #1		! Words to move
	jb	bytecopy	! No, no, bytes!
	rep
	movw			! Do the word copy
	jmp	copydone
bytecopy:
	rcl	cx, #1		! Get that lost bit back
	rep
	movsb			! Do the byte copy
copydone:
	mov	ax, ss		! Restore ds and es from the remaining ss
	mov	ds, ax
	mov	es, ax
	pop	di
	pop	si		! Restore C variable registers
	ret

! void raw_clear(off, seg, count)
!	Set count bytes at seg:off to zero.
.define _raw_clear
_raw_clear:
	mov	bx, sp
	push	di
	les	di, 2(bx)	! es:di = seg:off
	xor	ax, ax		! Zero
	mov	cx, 6(bx)	! Count
	shr	cx, #1		! Words to clear
	jb	byteclear	! No, no, bytes!
	rep
	stos			! Clear
	jmp	cleardone
byteclear:
	rcl	cx, #1		! Get that lost bit back
	rep
	stosb			! Byte clear
cleardone:
	mov	ax, ds		! Restore es from ds
	mov	es, ax
	pop	di
	ret

! u16_t get_word(off, seg);
! void put_word(off, seg, word);
!	Read or write a 16 bits word at an arbitrary location.
.define	_get_word, _put_word
_get_word:
	mov	bx, sp
	push	ds		! Save ds
	lds	bx, 2(bx)	! ds:bx = seg:off
	mov	ax, (bx)	! Get the word in ax
	pop	ds		! Restore ds
	ret
_put_word:
	mov	bx, sp
	push	ds
	mov	ax, 6(bx)	! Word to store at seg:off
	lds	bx, 2(bx)
	mov	(bx), ax	! Store the word
	pop	ds
	ret

! void relocate(void);
!	After the program has copied itself to a safer place, it needs to change
!	the segment registers.  Cseg has already been set to the new location.
.define _relocate
_relocate:
	pop	bx		! Return address
	mov	ax, cs		! Old code segment
	sub	ax, _cseg	! ax = -(new - old) = -Moving offset
	mov	dx, ds
	sub	dx, ax
	mov	ds, dx		! ds += (new - old)
	mov	es, dx
	mov	ss, dx
	mov	_dseg, dx
	push	_cseg		! New text segment
	push	bx		! Return offset of this function
	retf			! Relocate

! void *brk(void *addr)
! void *sbrk(size_t incr)
!	Cannot fail implementations of brk(2) and sbrk(3), so we can use
!	malloc(3).  They reboot on stack collision instead of returning -1.
.data
	.align	2
break:	.data2	_end		! A fake heap pointer
.text
.define _brk, __brk, _sbrk, __sbrk
_brk:
__brk:				! __brk is for the standard C compiler
	xor	ax, ax
	jmp	sbrk		! break= 0; return sbrk(addr);
_sbrk:
__sbrk:
	mov	ax, break	! ax= current break
sbrk:	push	ax		! save it as future return value
	mov	bx, sp		! Stack is now: (retval, retaddr, incr, ...)
	add	ax, 4(bx)	! ax= break + increment
	mov	break, ax	! Set new break
	lea	dx, -1024(bx)	! sp minus a bit of breathing space
	cmp	dx, ax		! Compare with the new break
	jb	heaperr		! Suffocating noises
	lea	dx, -4096(bx)	! A warning when heap+stack goes < 4K
	cmp	dx, ax
	jae	plenty		! No reason to complain
	mov	ax, #memwarn
	push	ax
	call	_printk		! Warn about memory running low
	pop	ax
	movb	memwarn, #0	! No more warnings
plenty:	pop	ax		! Return old break (0 for brk)
	ret
heaperr:mov	ax, #chmem
	push	ax
	mov	ax, #nomem
	push	ax
	call	_printk
	call	_reboot
.data
nomem:	.ascii	"\nOut of%s\0"
memwarn:.ascii	"\nLow on"
chmem:	.ascii	" memory, use chmem to increase the heap\n\0"
.text

! int dev_geometry(void);
!	Given the device "_device" and floppy disk parameters "_dskpars",
!	set the number of heads and sectors.  It returns true iff the device
!	exists.
.define	_dev_geometry
_dev_geometry:
	movb	dl, _device	! The default device
	cmpb	dl, #0x80	! Floppy < 0x80, winchester >= 0x80
	jae	winchester
floppy:
	int	0x11		! Get equipment configuration
	testb	al, #0x01	! Bit 0 set if floppies available
	jz	geoerr		! No floppy drives on this box
	shl	ax, #1		! Highest floppy drive # in bits 6-7
	shl	ax, #1		! Now in bits 0-1 of ah
	andb	ah, #0x03	! Extract bits
	cmpb	dl, ah		! Must be dl <= ah for drive to exist
	ja	geoerr		! Alas no drive dl.
	movb	dh, #2
	movb	_heads, dh	! Number of heads for this device
	mov	bx, #_dskpars	! bx = disk parameters
	movb	cl, SECTORS(bx)
	movb	_sectors, cl	! Sectors per track
	xor	ax, ax
	mov	es, ax		! es = 0 = vector segment
	eseg
	mov	DSKBASE+0, bx
	eseg
	mov	DSKBASE+2, ds	! DSKBASE+2:DSKBASE+0 = ds:bx = floppy parms
	jmp	geoboth
winchester:
	movb	ah, #0x08	! Code for drive parameters
	int	0x13		! dl still contains drive
	jb	geoerr		! No such drive?
	andb	cl, #0x3F	! cl = max sector number (1-origin)
	movb	_sectors, cl	! Number of sectors per track
	incb	dh		! dh = 1 + max head number (0-origin)
	movb	_heads, dh
geoboth:
	mov	ax, #1		! Code for success
	push	ax
	movb	al, cl		! al = sectors per track
	mulb	dh		! ax = heads * sectors
	mov	secspcyl, ax	! Sectors per cylinder = heads * sectors
geodone:push	ds
	pop	es		! Restore es register
	pop	ax		! Return code
	ret
geoerr:	xor	ax, ax
	push	ax
	jmp	geodone
.bss
secspcyl:	.zerow 1
.text

! int readsectors(bufoff, bufseg, address, count)
! int writesectors(bufoff, bufseg, address, count)
!	Read/write several sectors from/to disk or floppy.  The buffer must
!	be between 64K boundaries!  Count must fit in a byte.  The external
!	variables _device, _sectors and _heads describe the disk and its
!	geometry.  Returns 0 for success, otherwise the BIOS error code.
!
.define _readsectors, _writesectors
_writesectors:
	push	bp
	mov	bp, sp
	movb	13(bp), #3	! Code for a disk write
	jmp	rwsec
_readsectors:
	push	bp
	mov	bp, sp
	movb	13(bp), #2	! Code for a disk read
rwsec:	push	di
	push	es
	les	bx, 4(bp)	! es:bx = bufseg:bufoff
	mov	di, #3		! Execute 3 resets on floppy error
	cmpb	_device, #0x80
	jb	nohd
	mov	di, #1		! But only 1 reset on hard disk error
nohd:	cmpb	12(bp), #0	! count equals zero?
	jz	done
more:	mov	ax, 8(bp)
	mov	dx, 10(bp)	! dx:ax = address.  Divide it by sectors/cyl
	div	secspcyl	! ax = cylinder, dx = sector within cylinder
	xchg	ax, dx		! ax = sector within cylinder, dx = cylinder
	movb	ch, dl		! ch = low 8 bits of cylinder
	divb	_sectors	! al = head, ah = sector (0-origin)
	xorb	dl, dl		! About to shift bits 8-9 of cylinder into dl
	shr	dx, #1
	shr	dx, #1		! dl[6..7] = high cylinder
	orb	dl, ah		! dl[0..5] = sector (0-origin)
	movb	cl, dl		! cl[0..5] = sector, cl[6..7] = high cyl
	incb	cl		! cl[0..5] = sector (1-origin)
	movb	dh, al		! dh = head
	movb	dl, _device	! dl = device to use
	movb	al, _sectors	! Sectors per track - Sector number (0-origin)
	subb	al, ah		! = Sectors left on this track
	cmpb	al, 12(bp)	! Compare with # sectors to transfer
	jbe	doit		! Can't go past the end of a cylinder?
	movb	al, 12(bp)	! 12(bp) < sectors left on this track
doit:	movb	ah, 13(bp)	! Code for disk read (2) or write (3)
	push	ax		! Save al = sectors to read
	int	0x13		! call the BIOS to do the transfer
	pop	cx		! Restore al in cl
	jb	ioerr		! I/O error
	movb	al, cl		! Restore al = sectors read
	addb	bh, al		! bx += 2 * al * 256 (add bytes transferred)
	addb	bh, al		! es:bx = where next sector is located
	add	8(bp), ax	! Update address by sectors transferred
	adc	10(bp), #0	! Don't forget high word
	subb	12(bp), al	! Decrement sector count by sectors transferred
	jnz	more		! Not all sectors have been transferred
done:	call	wheel		! Display tricks
	xorb	ah, ah		! No error here!
	jmp	finish
ioerr:	dec	di		! Do we allow another reset?
	jl	finish		! No, report the error
	xorb	ah, ah		! Code for a reset (0)
	int	0x13
	jae	more		! Succesful reset, try request again
finish:	movb	al, ah
	xorb	ah, ah		! ax = error number
	pop	es
	pop	di
	pop	bp
	ret

! int getchar(), peekchar();
!	Read a character from the keyboard, or just look if there is one.
!	A carriage return is changed into a linefeed for UNIX compatibility.
!	The scancode of the character '=' is saved in eqscancode if it is
!	typed, because it can be used to recognize an Olivetti keyboard.
.define _getchar, _peekchar
_peekchar:
	movb	ah, #1		! Keyboard status
	int	0x16
	jnz	getc		! Keypress?
	xor	ax, ax		! No key
	ret
_getchar:
	movb	ah, #0		! Read character from keyboard
	int	0x16
getc:	cmpb	al, #0x3D	! '=' character
	jnz	noeq
	movb	_eqscancode, ah	! Save scancode of '=' key
noeq:	cmpb	al, #0x0D	! Carriage return
	jnz	nocr
	movb	al, #0x0A	! Change to linefeed
nocr:	xorb	ah, ah		! ax = al
	ret

! int putchar(c);
!	Write a character in teletype mode.  The putc and putk synonyms
!	are for the kernel printk function that uses one of them.
!	Newlines are automatically prepended by a carriage return.
!
.define _putchar, _putc, _putk
_putchar:
_putc:
_putk:	mov	bx, sp
	movb	al, 2(bx)	! al = character to be printed
	testb	al, al		! 1.6.* printk prints null characters
	jz	nulch		! that appear as blanks, so don't do it.
	cmpb	al, #0x0A	! al = newline?
	jnz	putc		! No
	cmpb	wdirty, #0	! Yes, erase wheel and do a carriage return
	jz	nodirt
	movb	al, #0x20	! putc(' ');
	call	putc
nodirt:	movb	al, #0x0D	! putc('\r')
	call	putc
	movb	al, #0x0A	! Restore the '\n' and print it
putc:	movb	ah, #14		! 14 = print character in teletype mode
	mov	bx, #0x0001	! Page 0, foreground color
	int	0x10		! Call BIOS VIDEO_IO
	movb	wdirty, #0
nulch:	ret

! |/-\|/-\|/-\|/-\|/-\	(playtime)
wheel:	push	si
	mov	si, gp		! si = gp
	lodsb
	call	putc		! putc(*si++)
	movb	al, #0x08
	call	putc		! putc('\b')
	cmp	si, #glyphs+4
	jne	wmore
	mov	si, #glyphs
wmore:	mov	gp, si		! gp= si == glyphs + 4 ? glyphs : si;
	incb	wdirty
	pop	si
	ret
.data
	.align	2
gp:	.data2	glyphs
glyphs:	.ascii	"|/-\\"
wdirty:	.data1	0
.text

! void reset_video(int color);
!	Reset and clear the screen.
! void set_cursor(column, row);
!	Position the cursor at a specified column & row.
.define _reset_video, _set_cursor
_reset_video:
	mov	bx, sp
	movb	al, #7		! Assume mode 7: 80x24 monochrome
	cmp	2(bx), #0	! Or is color requested?
	jz	clear
	movb	al, #2		! Mode 2: 80x25 color
clear:	xorb	ah, ah		! Reset video
	int	0x10
	xor	dx, dx		! dl = column = 0, dh = row = 0
	jmp	setcur
_set_cursor:
	mov	bx, sp
	movb	dl, 2(bx)	! dl = column
	movb	dh, 4(bx)	! dh = row
setcur:	xorb	bh, bh		! Page 0
	movb	ah, #0x02	! Set cursor position
	int	0x10
	ret

! u32_t get_tick(void);
!	Return the current value of the clock tick counter.  This counter
!	increments 18.2 times per second.  Poll it to do delays.  Does not
!	work on the original PC, but works on the PC/XT.
.define _get_tick
_get_tick:
	xorb	ah, ah		! Code for get tick count
	int	0x1A
	mov	ax, dx
	mov	dx, cx		! dx:ax = cx:dx = tick count
	ret


! Functions used to obtain info about the hardware, like the type of video
! and amount of memory.  Boot uses this information itself, but will also
! pass them on to a pure 386 kernel, because one can't make BIOS calls from
! protected mode.  The video type could probably be determined by the kernel
! too by looking at the hardware, but there is a small chance on errors that
! the monitor allows you to correct by setting variables.

.define		_get_video	! returns type of display
.define		_get_ext_memsize  ! returns amount of extended memory in K
.define		_get_low_memsize  ! returns amount of low memory in K
.define		_get_processor	! returns processor type (86, 186, 286, 386)

! u16_t get_video(void)
!	Return type of display, in order: MDA, CGA, mono EGA, color EGA,
!	mono VGA, color VGA.

_get_video:
	mov	ax, #0x1A00	! Function 1A returns display code
	int	0x10		! al = 1A if supported
	cmpb	al, #0x1A
	jnz	no_dc		! No display code function supported

	mov	ax, #2
	cmpb	bl, #5		! Is it a monochrome EGA?
	jz	got_video
	inc	ax
	cmpb	bl, #4		! Is it a color EGA?
	jz	got_video
	inc	ax
	cmpb	bl, #7		! Is it a monochrome VGA?
	jz	got_video
	inc	ax
	cmpb	bl, #8		! Is it a color VGA?
	jz	got_video

no_dc:	movb	ah, #0x12	! Get information about the EGA
	movb	bl, #0x10
	int	0x10
	cmpb	bl, #0x10	! Did it come back as 0x10? (No EGA)
	jz	no_ega

	mov	ax, #2
	cmpb	bh, #1		! Is it monochrome?
	jz	got_video
	inc	ax
	jmp	got_video

no_ega:	int	0x11		! Get bit pattern for equipment
	and	ax, #0x30	! Isolate color/mono field
	sub	ax, #0x30
	jz	got_video	! Is it an MDA?
	mov	ax, #1		! No it's CGA

got_video:
	ret

! u16_t get_ext_memsize(void);
!	Ask the BIOS how much extended memory there is.

_get_ext_memsize:
	call	_get_processor	! Is this an old crate?
	cmp	ax, #186
	jbe	no_ext		! Don't try the next function, it crashed an XT
	movb	ah, #0x88
	clc			! Carry will stay clear if call exists
	int	0x15		! Returns size (in K) in ax for AT's
	jnb	got_ext_memsize
no_ext:	sub	ax, ax		! Error, probably a PC
got_ext_memsize:
	ret

! u16_t get_low_memsize(void);
!	Ask the BIOS how much normal memory there is.

_get_low_memsize:
	int	0x12		! Returns the size (in K) in ax
	ret

! u16_t get_processor(void);
!	Decide processor type among 8088=8086, 80188=80186, 80286, 80386, 80486.
!	Return 86, 186, 286, 386 or 486.
!	Preserves all registers except the flags and the return register ax.

! Method:
!	8088=8086 and 80188=80186 push sp as new sp, 80286 and 80386 as old sp.
!	All but 8088=8086 do shifts mod 32 or 16.
!	386 stores 0 for the upper 8 bits of the GDT pointer in 16 bit mode,
!	while 286 stores 0xFF.
!	486 has an AC flag the 386 doesn't have.

	o32 = 0x66		! 32 bit operand size prefix

_get_processor:
	push	bp
	mov	bp, sp
	push	sp		! see if pushed sp == sp
	pop	ax
	cmp	ax, sp
	jz	new_processor
	mov	cx, #0x0120	! see if shifts are mod 32
	shlb	ch, cl		! zero tells if 86
	mov	ax, #86
	jz	got_processor
	mov	ax, #186
	jmp	got_processor

new_processor:			! see if high bits are set in saved GDT
	sub	sp, #6		! space for GDT ptr
	.data1	0x0F		! Prefix for 286 instruction: sgdt -6(bp)
	add	-6(bp), ax	! save 3 word GDT ptr (This is NOT an add!)
	cmpb	-1(bp), #0	! top byte of GDT ptr is zero only for 386
	mov	ax, #286
	jnz	got_processor

! 386 or 486
	and	sp, #0xFFFC	! Align stack to avoid AC fault (needed?)
	.data1	o32		! About to operate on a 32 bit object
	pushf			! Push eflags
	pop	ax
	pop	dx		! dx:ax = eflags
	mov	bx, ax
	mov	cx, dx		! Save original eflags
	xor	dx, #0x0004	! Flip the AC bit
	push	dx
	push	ax		! Push modified eflags value
	.data1	o32
	popf			! Load modified eflags register
	.data1	o32
	pushf
	pop	ax
	pop	dx		! Get it again
	push	cx
	push	bx
	.data1	o32
	popf			! Restore original eflags register
	xor	dx, cx		! See if AC bit changed
	test	dx, #0x0004
	mov	ax, #386	! 386 if it didn't react to "flipping"
	jz	got_processor
	mov	ax, #486	! 486 if you can modify the AC bit

got_processor:
	mov	sp, bp
	pop	bp
	ret

! void _bootstrap(device, partoff, partseg)
!	Call another bootstrap routine to boot MS-DOS for instance.  (No real
!	need for that anymore, now that you can format floppies under Minix).
!	The bootstrap must have been loaded at BOOTSEG from "device".
.define _bootstrap
_bootstrap:
	mov	bx, sp
	movb	dl, 2(bx)	! Device to boot from
	lds	si, 4(bx)	! ds:si = partition table entry
	xor	ax, ax
	mov	es, ax		! Vector segment
	mov	di, #BUFFER	! es:di = buffer in low core
	mov	cx, #PENTRYSIZE	! cx = size of partition table entry
	rep
	movsb			! Copy the entry to low core
	mov	si, #BUFFER	! es:si = partition table entry
	mov	ds, ax		! Some bootstraps need zero segment registers
	cli
	mov	ss, ax
	mov	sp, #BOOTOFF	! This should do it
	sti
	jmpf	BOOTOFF, 0	! Back to where the BIOS loads the boot code

! To my surprise this code is so fast that floppy drive 0 was still running
! (tries to boot floppy first), when Minix was started after a hard disk boot.
stop_motor:
	mov	dx, #0x03F2	! Motor drive control bits
	movb	al, #0x0C	! Bits 4-7 for floppy 0-3 are off
	outb			! Kill the motors
	ret

! void minix86(koff, kcs, kds, bootparams, paramsize);
!	Call 8086 Minix or 386 Minix with an 8086 startoff.
.define _minix86
_minix86:
	call	stop_motor	! Turn off floppy drives
	mov	bp, sp		! Pointer to arguments
	mov	ax, #0x100	! Newer boot code
	mov	si, 8(bp)
	mov	di, ds		! di:si = boot parameters
	mov	cx, 10(bp)	! # bytes of boot parameters
	push	cx
	push	si		! Kernel may find these on the stack too
	mov	ds, 6(bp)	! Kernel data segment set
	mov	es, 6(bp)	! Set es to kernel data too
	cli			! Disable interrupts
	jmpf	@2(bp)		! Finally out of this mess!

! Minix is called with ax, di and si as expected, but also with segment
! registers and stack compatible with the 386 call.

! void minix386(koff, kcs, kds, bootparams, paramsize);
!	Call 386 Minix with a 386 mode switch.  Code inspired by the Amoeba
!	386 bootstrap by Leendert van Doorn.
.define _minix386
_minix386:
	call	stop_motor	! Turn off floppy drives
	mov	bp, sp		! Pointer to arguments

	mov	di, ds		! Monitor ds
	mov	si, #gdt	! di:si = Global descriptor table
	mov	bx, #gdt_desc
	call	set_base	! Set base of gdt descriptor

	mov	di, 6(bp)	! Kernel ds
	xor	si, si		! di:si = Kernel data segment
	mov	bx, #ds_desc
	call	set_base	! Set base of kernel ds

	mov	di, ss		! Monitor ss
	xor	si, si		! di:si = Monitor stack segment
	mov	bx, #ss_desc
	call	set_base	! Minix starts with the stack of the monitor

	mov	di, 4(bp)	! Kernel cs
	xor	si, si		! di:si = Kernel text segment
	mov	bx, #cs_desc
	call	set_base	! Set base of kernel cs

	xor	ax, ax
	push	ax
	push	10(bp)		! 32 bit size of parameters on stack
	push	ax
	push	8(bp)		! 32 bit address of parameters (ss relative)

! Use the BIOS to kick us into protected mode.  This is the most portable
! way to enable the A20 address line.  A real programmer would use cr0.

	mov	si, #gdt	! es:si = global descriptor table
	xor	bx, bx		! 8259's must be initialized by the kernel
	movb	ah, #0x89	! Protected mode function code
! Fake an interrupt stack frame as if called from the kernel entry point
	cli			! No more interruptions
	pushf			! Flags
	push	4(bp)		! Kernel cs
	push	2(bp)		! Kernel entry point
	mov	ds, bx		! ds = vector segment
	jmpf	@4*0x15		! "int 0x15"

! The "interrupt" will return directly to the Minix kernel in 386 mode.  The
! split is clean:  No 386 code here, and no 8086 code in the kernel.  The
! boot parameters address and size are on the stack.  They may be retrieved
! using the ss descriptor.

set_base:
	! Set the base of descriptor bx to the 8086 address di:si
	mov	2(bx), di	! Base = segment
	mov	cx, #4
seg2abs:
	shl	2(bx), #1
	rclb	4(bx), #1	! Base = segment << 4
	loop	seg2abs
	add	2(bx), si
	adcb	4(bx), #0	! Base = (segment << 4) + offset
	ret

.data
	.align	2
	UNSET	= 0	! Must be computed

! Global descriptor table.
gdt:
null_desc:
	! Null descriptor
	.data2	0x0000, 0x0000
	.data1	0x00, 0x00, 0x00, 0x00
gdt_desc:
	! Descriptor for this descriptor table
	.data2	8*8-1, UNSET
	.data1	UNSET, 0x00, 0x00, 0x00
idt_desc:
	! Interrupt descriptor table descriptor (no interrupts allowed)
	.data2	0x0000, 0x0000
	.data1	0x00, 0x00, 0x00, 0x00
ds_desc:
	! Kernel data segment descriptor (4Gb flat)
	.data2	0xFFFF, UNSET
	.data1	UNSET, 0x92, 0xCF, 0x00
es_desc:
	! Physical memory descriptor (4Gb flat)
	.data2	0xFFFF, 0x0000
	.data1	0x00, 0x92, 0xCF, 0x00
ss_desc:
	! Monitor data segment descriptor (64Kb flat)
	.data2	0xFFFF, UNSET
	.data1	UNSET, 0x92, 0x40, 0x00
cs_desc:
	! Kernel code segment descriptor (4Gb flat)
	.data2	0xFFFF, UNSET
	.data1	UNSET, 0x9A, 0xCF, 0x00
bios_desc:
	! BIOS segment descriptor (scratch for int 0x15)
	.data2	UNSET, UNSET
	.data1	UNSET, UNSET, UNSET, UNSET
