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

! This file contains a number of assembly code utility routines needed by the
! kernel.  They are:

.define	_monitor	! exit Minix and return to the monitor
.define real2prot	! switch from real to protected mode
.define prot2real	! switch from protected to real mode
.define	_check_mem	! check a block of memory, return the valid size
.define	_cp_mess	! copies messages from source to destination
.define	_exit		! dummy for library routines
.define	__exit		! dummy for library routines
.define	___exit		! dummy for library routines
.define	.fat, .trp	! dummies for library routines
.define	_in_byte	! read a byte from a port and return it
.define	_in_word	! read a word from a port and return it
.define	_out_byte	! write a byte to a port
.define	_out_word	! write a word to a port
.define	_port_read	! transfer data from (disk controller) port to memory
.define	_port_read_byte	! likewise byte by byte
.define	_port_write	! transfer data from memory to (disk controller) port
.define	_port_write_byte ! likewise byte by byte
.define	_lock		! disable interrupts
.define	_unlock		! enable interrupts
.define	_enable_irq	! enable an irq at the 8259 controller
.define	_disable_irq	! disable an irq
.define	_phys_copy	! copy data from anywhere to anywhere in memory
.define	_mem_rdw	! copy one word from [segment:offset]
.define	_reset		! reset the system
.define	_mem_vid_copy	! copy data to video ram
.define	_vid_vid_copy	! move data in video ram
.define	_level0		! call a function at level 0
.define	klib_init_prot	! initialize klib functions for protected mode

! The routines only guarantee to preserve the registers the C compiler
! expects to be preserved (si, di, bp, sp, segment registers, and direction
! bit in the flags), though some of the older ones preserve bx, cx and dx.

#define DS_286_OFFSET	DS_286_INDEX*DESC_SIZE
#define ES_286_OFFSET	ES_286_INDEX*DESC_SIZE
#	define EM_XFER_FUNC	0x87
#define JMP_OPCODE	0xE9	/* opcode used for patching */
#define OFF_MASK	0x000F	/* offset mask for phys_b -> hclick:offset */
#define HCHIGH_MASK	0x0F	/* h/w click mask for low byte of hi word */
#define HCLOW_MASK	0xF0	/* h/w click mask for low byte of low word */

! Imported functions

.extern	p_restart
.extern	p_save
.extern	_restart
.extern	save

! Exported variables

.extern kernel_cs

! Imported variables

.extern kernel_ds
.extern _irq_use
.extern	_blank_color
.extern	_gdt
.extern	_protected_mode
.extern	_vid_seg
.extern	_vid_size
.extern	_vid_mask
.extern	_level0_func

	.text
!*===========================================================================*
!*				monitor					     *
!*===========================================================================*
! PUBLIC void monitor();
! Return to the monitor.

_monitor:
	call	prot2real		! switch to real mode
	mov	ax, _reboot_code+0	! address of new parameters
	mov	dx, _reboot_code+2
	mov	sp, _mon_sp		! restore monitor stack pointer
	mov	bx, _mon_ss		! monitor data segment
	mov	ds, bx
	mov	es, bx
	mov	ss, bx
	pop	di
	pop	si
	pop	bp
	retf				! return to the monitor


#if ENABLE_BIOS_WINI
!*===========================================================================*
!*				bios13					     *
!*===========================================================================*
! PUBLIC void bios13();
.define _bios13
_bios13:			! make a BIOS 0x13 call for disk I/O
	push	si
	push	di		! save C variable registers
	pushf			! save flags

	call	int13		! make the actual call

	popf			! restore flags
	pop	di		! restore C registers
	pop	si
	ret

! Make a BIOS 0x13 call from protected mode
p_bios13:
	push	bp
	push	si
	push	di			! save C variable registers
	pushf				! save flags
	cli				! no interruptions
	inb	INT2_CTLMASK
	movb	ah, al
	inb	INT_CTLMASK
	push	ax			! save interrupt masks
	mov	ax, _irq_use		! map of in-use IRQs
	and	ax, #~[1<<CLOCK_IRQ]	! there is a special clock handler
	outb	INT_CTLMASK		! enable all unused IRQs and vv.
	movb	al, ah
	outb	INT2_CTLMASK

	smsw	ax
	push	ax			! save machine status word
	call	prot2real		! switch to real mode

	call	int13			! make the actual call

	call	real2prot		! back to protected mode
	pop	ax
	lmsw	ax			! restore msw

	pop	ax			! restore interrupt masks
	outb	INT_CTLMASK
	movb	al, ah
	outb	INT2_CTLMASK
	popf				! restore flags
	pop	di
	pop	si
	pop	bp			! restore C variable registers
	ret

int13:
	mov	ax, _Ax		! load parameters
	mov	bx, _Bx
	mov	cx, _Cx
	mov	dx, _Dx
	mov	es, _Es
	sti			! enable interrupts
	int	0x13		! make the BIOS call
	cli			! disable interrupts
	mov	_Ax, ax		! save results
	mov	_Bx, bx
	mov	_Cx, cx
	mov	_Dx, dx
	mov	_Es, es
	mov	ax, ds
	mov	es, ax		! restore es
	ret

.bss
.define	_Ax, _Bx, _Cx, _Dx, _Es		! 8086 register variables
.comm	_Ax, 2
.comm	_Bx, 2
.comm	_Cx, 2
.comm	_Dx, 2
.comm	_Es, 2
.text
#endif /* ENABLE_BIOS_WINI */


!*===========================================================================*
!*				real2prot				     *
!*===========================================================================*
! Switch from real to protected mode.
real2prot:
	lgdt	_gdt+GDT_SELECTOR	! set global descriptor table
	smsw	ax
	mov	msw, ax			! save real mode msw
	orb	al, #0x01		! set PE (protection enable) bit
	lmsw	ax			! set msw, enabling protected mode

	jmpf	csinit, CS_SELECTOR	! set code segment selector
csinit:
	mov	ax, #DS_SELECTOR	! set data selectors
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	lidt    _gdt+IDT_SELECTOR	! set interrupt vectors
	andb	_gdt+TSS_SELECTOR+DESC_ACCESS, #~0x02  ! clear TSS busy bit
	mov	ax, #TSS_SELECTOR
	ltr	ax			! set TSS register

	movb	ah, #0xDF
	jmp	gate_A20		! enable the A20 address line


!*===========================================================================*
!*				prot2real				     *
!*===========================================================================*
! Switch from protected to real mode.
prot2real:
	mov	save_sp, sp		! save stack pointer
	cmp	_processor, #386	! is this a 386?
	jae	p2r386
p2r286:
	mov	_gdt+ES_286_OFFSET+DESC_BASE, #0x0400
	movb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE, #0x00
	mov	ax, #ES_286_SELECTOR
	mov	es, ax			! BIOS data segment
  eseg	mov	0x0067, #real		! set return from shutdown address
  cseg	mov	ax, kernel_cs
  eseg	mov	0x0069, ax
	movb	al, #0x8F
	outb	0x70			! select CMOS byte 0x0F (disable NMI)
	jmp	.+2
	movb	al, #0x0A
	outb	0x71			! set shutdown code to 0x0A "jump far"
	jmp	p_reset			! cause a processor shutdown
p2r386:
	lidt	idt_vectors		! real mode interrupt vectors
	push	_gdt+CS_SELECTOR+0
	push	_gdt+DS_SELECTOR+0	! save CS and DS limits
	mov	_gdt+CS_SELECTOR+0, #0xFFFF
	mov	_gdt+DS_SELECTOR+0, #0xFFFF ! set 64k limits
	jmpf	cs64k, CS_SELECTOR	! reload selectors
cs64k:	mov	ax, #DS_SELECTOR
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	pop	_gdt+DS_SELECTOR+0
	pop	_gdt+CS_SELECTOR+0	! restore CS and DS limits
	.data1	0x0F,0x20,0xC0		! mov	eax, cr0
	mov	ax, msw			! restore real mode (16 bits) msw
	.data1	0x0F,0x22,0xC0		! mov	cr0, eax
	.data1	0xEA			! jmpf real, "kernel_cs"
	.data2	real
kernel_cs:
	.data2	0
real:
  cseg	mov	ax, kernel_ds		! reload data segment registers
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, save_sp		! restore stack

	movb	ah, #0xDD
	!jmp	gate_A20		! disable the A20 address line

! Enable (ah = 0xDF) or disable (ah = 0xDD) the A20 address line.
gate_A20:
	call	kb_wait
	movb	al, #0xD1	! Tell keyboard that a command is coming
	outb	0x64
	call	kb_wait
	movb	al, ah		! Enable or disable code
	outb	0x60
	call	kb_wait
	mov	ax, #25		! 25 microsec delay for slow keyboard chip
0:	out	0xED		! Write to an unused port (1us)
	dec	ax
	jne	0b
	ret
kb_wait:
	inb	0x64
	testb	al, #0x02	! Keyboard input buffer full?
	jnz	kb_wait		! If so, wait
	ret


!*===========================================================================*
!*				check_mem				     *
!*===========================================================================*
! PUBLIC phys_bytes check_mem(phys_bytes base, phys_bytes size);
! Check a block of memory, return the amount valid.
! Only every 16th byte is checked.
! This only works in protected mode.
! An initial size of 0 means everything.
! This really should do some alias checks.

PCM_DENSITY	=	256	! resolution of check
				! the shift logic depends on this being 256
TEST1PATTERN	=	0x55	! memory test pattern 1
TEST2PATTERN	=	0xAA	! memory test pattern 2

_check_mem:
	pop	bx
	pop	_gdt+DS_286_OFFSET+DESC_BASE
	pop	ax		! pop base into base of source descriptor
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,al
	pop	cx		! byte count in dx:cx
	pop	dx
	sub	sp,#4+4
	push	bx
	push	ds

	sub	ax,ax		! prepare for early exit
	test	dx,#0xFF00
	jnz	cm_1exit	! cannot handle bases above 16M
	movb	cl,ch		! divide size by 256 and discard high byte
	movb	ch,dl
	push	cx		! save divided size
	sub	bx,bx		! test bytes at bases of segments
cm_loop:
	mov	ax,#DS_286_SELECTOR
	mov	ds,ax
	movb	dl,#TEST1PATTERN
	xchgb	dl,(bx)		! write test pattern, remember original value
	xchgb	dl,(bx)		! restore original value, read test pattern
	cmpb	dl,#TEST1PATTERN	! must agree if good real memory
	jnz	cm_exit		! if different, memory is unusable
	movb	dl,#TEST2PATTERN
	xchgb	dl,(bx)
	xchgb	dl,(bx)
	cmpb	dl,#TEST2PATTERN
	jnz	cm_exit
				! next segment, test for wraparound at 16M
				! assuming es == old ds
  eseg	add	_gdt+DS_286_OFFSET+DESC_BASE,#PCM_DENSITY
  eseg	adcb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,#0
	loopnz	cm_loop

cm_exit:
	pop	ax
	sub	ax,cx		! verified size in multiples of PCM_DENSITY
cm_1exit:
	movb	dl,ah		! convert to phys_bytes in dx:ax
	subb	dh,dh
	movb	ah,al
	movb	al,dh
	pop	ds
	ret


!*===========================================================================*
!*				cp_mess					     *
!*===========================================================================*
! PUBLIC void cp_mess(int src, phys_clicks src_clicks, vir_bytes src_offset,
!		      phys_clicks dst_clicks, vir_bytes dst_offset);
! This routine makes a fast copy of a message from anywhere in the address
! space to anywhere else.  It also copies the source address provided as a
! parameter to the call into the first word of the destination message.
!
! Note that the message size, "Msize" is in WORDS (not bytes) and must be set
! correctly.  Changing the definition of message in the type file and not
! changing it here will lead to total disaster.

_cp_mess:
	cld
	push es			! save es
	push ds			! save ds
	mov bx,sp		! index off bx because machine cannot use sp
	push si			! save si
	push di			! save di

	mov	ax,12(bx)	! destination click
#if HCLICK_SHIFT > CLICK_SHIFT
#error /* Small click sizes are not supported (right shift will lose bits). */
#endif
#if HCLICK_SHIFT < CLICK_SHIFT
	movb	cl,#CLICK_SHIFT-HCLICK_SHIFT
	shl	ax,cl		! destination segment
#endif
	mov	es,ax
	mov	di,14(bx)	! offset of destination message

! Be careful not to destroy ds before we are finished with the bx pointer.
! We are using bx and not the more natural bp to save pushing bp.

	mov	ax,6(bx)	! process number of sender
	mov	si,10(bx)	! offset of source message
	mov	bx,8(bx)	! source click (finished with bx as a pointer)
#if HCLICK_SHIFT < CLICK_SHIFT
	shl	bx,cl		! source segment
#endif
	mov	ds,bx

	stos			! copy process number of sender to dest message
	add si,*2		! do not copy first word
	mov cx,*Msize-1		! remember, first word does not count
	rep			! iterate cx times to copy 11 words
	movs			! copy the message
	pop di			! restore di
	pop si			! restore si
	pop ds			! restore ds
	pop es			! restore es
	ret			! that is all folks!


!*===========================================================================*
!*				exit					     *
!*===========================================================================*
! PUBLIC void exit();
! Some library routines use exit, so provide a dummy version.
! Actual calls to exit cannot occur in the kernel.
! Same for .fat & .trp.

_exit:
__exit:
___exit:
.fat:
.trp:
	sti
	jmp __exit


!*===========================================================================*
!*				in_byte					     *
!*===========================================================================*
! PUBLIC unsigned in_byte(port_t port);
! Read an (unsigned) byte from the i/o port  port  and return it.

_in_byte:
	pop	bx
	pop	dx		! port
	dec	sp
	dec	sp
	inb			! input 1 byte
	subb	ah,ah		! unsign extend
	jmp	(bx)


!*===========================================================================*
!*				in_word					     *
!*===========================================================================*
! PUBLIC unsigned short in_word(port_t port);
! Read an (unsigned) word from the i/o port and return it.

_in_word:
	pop	bx
	pop	dx		! port
	dec	sp
	dec	sp		! added to new klib.s 3/21/91 d.e.c.
	inw			! input 1 word no sign extend needed
	jmp	(bx)


!*===========================================================================*
!*				out_byte				     *
!*===========================================================================*
! PUBLIC void out_byte(port_t port, int value);
! Write  value  (cast to a byte)  to the I/O port  port.

_out_byte:
	pop	bx
	pop	dx		! port
	pop	ax		! value
	sub	sp,#2+2
	outb			! output 1 byte
	jmp	(bx)


!*===========================================================================*
!*				out_word				     *
!*===========================================================================*
! PUBLIC void out_word(port_t port, int value);
! Write  value  (cast to a word)  to the I/O port  port.

_out_word:
	pop	bx
	pop	dx		! port
	pop	ax		! value
	sub	sp,#2+2
	outw			! output 1 word
	jmp	(bx)


!*===========================================================================*
!*				port_read				     *
!*===========================================================================*
! PUBLIC void port_read(port_t port, phys_bytes destination,unsigned bytcount);
! Transfer data from (hard disk controller) port to memory.

_port_read:
	push	bp
	mov	bp,sp
	push	di
	push	es

	call	portio_setup
	shr	cx,#1		! count in words
	mov	di,bx		! di = destination offset
	mov	es,ax		! es = destination segment
	rep
	ins

	pop	es
	pop	di
	pop	bp
	ret

portio_setup:
	mov	ax,4+2(bp)	! source/destination address in dx:ax
	mov	dx,4+2+2(bp)
	mov	bx,ax
	and	bx,#OFF_MASK	! bx = offset = address % 16
	andb	dl,#HCHIGH_MASK	! ax = segment = address / 16 % 0x10000
	andb	al,#HCLOW_MASK
	orb	al,dl
	movb	cl,#HCLICK_SHIFT
	ror	ax,cl
	mov	cx,4+2+4(bp)	! count in bytes
	mov	dx,4(bp)	! port to read from
	cld			! direction is UP
	ret


!*===========================================================================*
!*				port_read_byte				     *
!*===========================================================================*
! PUBLIC void port_read_byte(port_t port, phys_bytes destination,
!							unsigned bytcount);
! Transfer data port to memory.

_port_read_byte:
	push	bp
	mov	bp,sp
	push	di
	push	es

	call	portio_setup
	mov	di,bx		! di = destination offset
	mov	es,ax		! es = destination segment
	rep
	insb

	pop	es
	pop	di
	pop	bp
	ret


!*===========================================================================*
!*				port_write				     *
!*===========================================================================*
! PUBLIC void port_write(port_t port, phys_bytes source, unsigned bytcount);
! Transfer data from memory to (hard disk controller) port.

_port_write:
	push	bp
	mov	bp,sp
	push	si
	push	ds

	call	portio_setup
	shr	cx,#1		! count in words
	mov	si,bx		! si = source offset
	mov	ds,ax		! ds = source segment
	rep
	outs

	pop	ds
	pop	si
	pop	bp
	ret


!*===========================================================================*
!*				port_write_byte				     *
!*===========================================================================*
! PUBLIC void port_write_byte(port_t port, phys_bytes source,
!							unsigned bytcount);
! Transfer data from memory to port.

_port_write_byte:
	push	bp
	mov	bp,sp
	push	si
	push	ds

	call	portio_setup
	mov	si,bx		! si = source offset
	mov	ds,ax		! ds = source segment
	rep
	outsb

	pop	ds
	pop	si
	pop	bp
	ret


!*===========================================================================*
!*				lock					     *
!*===========================================================================*
! PUBLIC void lock();
! Disable CPU interrupts.

_lock:
	cli				! disable interrupts
	ret


!*===========================================================================*
!*				unlock					     *
!*===========================================================================*
! PUBLIC void unlock();
! Enable CPU interrupts.

_unlock:
	sti
	ret


!*==========================================================================*
!*				enable_irq				    *
!*==========================================================================*/
! PUBLIC void enable_irq(unsigned irq)
! Enable an interrupt request line by clearing an 8259 bit.
! Equivalent code for irq < 8:
!	out_byte(INT_CTLMASK, in_byte(INT_CTLMASK) & ~(1 << irq));

_enable_irq:
	mov	bx, sp
	mov	cx, 2(bx)		! irq
	pushf
	cli
	movb	ah, #~1
	rolb	ah, cl			! ah = ~(1 << (irq % 8))
	cmpb	cl, #8
	jae	enable_8		! enable irq >= 8 at the slave 8259
enable_0:
	inb	INT_CTLMASK
	andb	al, ah
	outb	INT_CTLMASK		! clear bit at master 8259
	popf
	ret
enable_8:
	inb	INT2_CTLMASK
	andb	al, ah
	outb	INT2_CTLMASK		! clear bit at slave 8259
	popf
	ret


!*==========================================================================*
!*				disable_irq				    *
!*==========================================================================*/
! PUBLIC int disable_irq(unsigned irq)
! Disable an interrupt request line by setting an 8259 bit.
! Equivalent code for irq < 8:
!	out_byte(INT_CTLMASK, in_byte(INT_CTLMASK) | (1 << irq));
! Returns true iff the interrupt was not already disabled.

_disable_irq:
	mov	bx, sp
	mov	cx, 2(bx)		! irq
	pushf
	cli
	movb	ah, #1
	rolb	ah, cl			! ah = (1 << (irq % 8))
	cmpb	cl, #8
	jae	disable_8		! disable irq >= 8 at the slave 8259
disable_0:
	inb	INT_CTLMASK
	testb	al, ah
	jnz	dis_already		! already disabled?
	orb	al, ah
	outb	INT_CTLMASK		! set bit at master 8259
	popf
	mov	ax, #1			! disabled by this function
	ret
disable_8:
	inb	INT2_CTLMASK
	testb	al, ah
	jnz	dis_already		! already disabled?
	orb	al, ah
	outb	INT2_CTLMASK		! set bit at slave 8259
	popf
	mov	ax, #1			! disabled by this function
	ret
dis_already:
	popf
	xor	ax, ax			! already disabled
	ret


!*===========================================================================*
!*				phys_copy				     *
!*===========================================================================*
! PUBLIC void phys_copy(phys_bytes source, phys_bytes destination,
!			phys_bytes bytecount);
! Copy a block of physical memory.

SRCLO	=	4
SRCHI	=	6
DESTLO	=	8
DESTHI	=	10
COUNTLO	=	12
COUNTHI	=	14

_phys_copy:
	push	bp		! save only registers required by C
	mov	bp,sp		! set bp to point to source arg less 4

	push	si		! save si
	push	di		! save di
	push	ds		! save ds
	push	es		! save es

	mov	ax,SRCLO(bp)	! dx:ax = source address (dx is NOT segment)
	mov	dx,SRCHI(bp)
	mov	si,ax		! si = source offset = address % 16
	and	si,#OFF_MASK
	andb	dl,#HCHIGH_MASK	! ds = source segment = address / 16 % 0x10000
	andb	al,#HCLOW_MASK
	orb	al,dl		! now bottom 4 bits of dx are in ax
	movb	cl,#HCLICK_SHIFT ! rotate them to the top 4
	ror	ax,cl
	mov	ds,ax

	mov	ax,DESTLO(bp)	! dx:ax = destination addr (dx is NOT segment)
	mov	dx,DESTHI(bp)
	mov	di,ax		! di = dest offset = address % 16
	and	di,#OFF_MASK
	andb	dl,#HCHIGH_MASK	! es = dest segment = address / 16 % 0x10000
	andb	al,#HCLOW_MASK
	orb	al,dl
	ror	ax,cl
	mov	es,ax

	mov	ax,COUNTLO(bp)	! dx:ax = remaining count
	mov	dx,COUNTHI(bp)

! copy upwards (cannot handle overlapped copy)

pc_loop:
	mov	cx,ax		! provisional count for this iteration
	test	ax,ax		! if count >= 0x8000, only do 0x8000 per iter
	js	pc_bigcount	! low byte already >= 0x8000
	test	dx,dx
	jz	pc_upcount	! less than 0x8000
pc_bigcount:
	mov	cx,#0x8000	! use maximum count per iteration
pc_upcount:
	sub	ax,cx		! update count
	sbb	dx,#0		! cannot underflow, so carry clear now for rcr
	rcr	cx,#1		! count in words, carry remembers if byte
	jnb	pc_even		! no odd byte
	movb			! copy odd byte
pc_even:
	rep			! copy 1 word at a time
	movs			! word copy

	mov	cx,ax		! test if remaining count is 0
	or	cx,dx
	jnz	pc_more		! more to do

	pop	es		! restore es
	pop	ds		! restore ds
	pop	di		! restore di
	pop	si		! restore si
	pop	bp		! restore bp
	ret			! return to caller

pc_more:
	sub	si,#0x8000	! adjust pointers so the offset does not
	mov	cx,ds		! overflow in the next 0x8000 bytes
	add	cx,#0x800	! pointers end up same physical location
	mov	ds,cx		! the current offsets are known >= 0x8000
	sub	di,#0x8000	! since we just copied that many
	mov	cx,es
	add	cx,#0x800
	mov	es,cx
	jmp	pc_loop		! start next iteration


!*===========================================================================*
!*				mem_rdw					     *
!*===========================================================================*
! PUBLIC u16_t mem_rdw(u16_t segment, u16_t *offset);
! Load and return the word at the far pointer  segment:offset.

_mem_rdw:
	mov	cx,ds		! save ds
	pop	dx		! return adr
	pop	ds		! segment
	pop	bx		! offset
	sub	sp,#2+2		! adjust for parameters popped
	mov	ax,(bx)		! load the word to return
	mov	ds,cx		! restore ds
	jmp	(dx)		! return


!*===========================================================================*
!*				reset					     *
!*===========================================================================*
! PUBLIC void reset();
! Reset the system.
! In real mode we simply jump to the reset address.

_reset:
	jmpf	0,0xFFFF


!*===========================================================================*
!*				mem_vid_copy				     *
!*===========================================================================*
! PUBLIC void mem_vid_copy(u16 *src, unsigned dst, unsigned count);
!
! Copy count characters from kernel memory to video memory.  Src, dst and
! count are character (word) based video offsets and counts.  If src is null
! then screen memory is blanked by filling it with blank_color.

MVC_ARGS	=	2 + 2 + 2 + 2	! 2 + 2 + 2
!			es  di  si  ip	 src dst ct

_mem_vid_copy:
	push	si
	push	di
	push	es
	mov	bx, sp
	mov	si, MVC_ARGS(bx)	! source
	mov	di, MVC_ARGS+2(bx)	! destination
	mov	dx, MVC_ARGS+2+2(bx)	! count
	mov	es, _vid_seg		! destination is video segment
	cld				! make sure direction is up
mvc_loop:
	and	di, _vid_mask		! wrap address
	mov	cx, dx			! one chunk to copy
	mov	ax, _vid_size
	sub	ax, di
	cmp	cx, ax
	jbe	0f
	mov	cx, ax			! cx = min(cx, vid_size - di)
0:	sub	dx, cx			! count -= cx
	shl	di, #1			! byte address
	test	si, si			! source == 0 means blank the screen
	jz	mvc_blank
mvc_copy:
	rep				! copy words to video memory
	movs
	jmp	mvc_test
mvc_blank:
	mov	ax, _blank_color	! ax = blanking character
	rep
	stos				! copy blanks to video memory
	!jmp	mvc_test
mvc_test:
	shr	di, #1			! word addresses
	test	dx, dx
	jnz	mvc_loop
mvc_done:
	pop	es
	pop	di
	pop	si
	ret


!*===========================================================================*
!*				vid_vid_copy				     *
!*===========================================================================*
! PUBLIC void vid_vid_copy(unsigned src, unsigned dst, unsigned count);
!
! Copy count characters from video memory to video memory.  Handle overlap.
! Used for scrolling, line or character insertion and deletion.  Src, dst
! and count are character (word) based video offsets and counts.

VVC_ARGS	=	2 + 2 + 2 + 2	! 2 + 2 + 2
!			es  di  si  ip 	 src dst ct

_vid_vid_copy:
	push	si
	push	di
	push	es
	mov	bx, sp
	mov	si, VVC_ARGS(bx)	! source
	mov	di, VVC_ARGS+2(bx)	! destination
	mov	dx, VVC_ARGS+2+2(bx)	! count
	mov	es, _vid_seg		! use video segment
	cmp	si, di			! copy up or down?
	jb	vvc_down
vvc_up:
	cld				! direction is up
vvc_uploop:
	and	si, _vid_mask		! wrap addresses
	and	di, _vid_mask
	mov	cx, dx			! one chunk to copy
	mov	ax, _vid_size
	sub	ax, si
	cmp	cx, ax
	jbe	0f
	mov	cx, ax			! cx = min(cx, vid_size - si)
0:	mov	ax, _vid_size
	sub	ax, di
	cmp	cx, ax
	jbe	0f
	mov	cx, ax			! cx = min(cx, vid_size - di)
0:	sub	dx, cx			! count -= cx
	shl	si, #1
	shl	di, #1			! byte addresses
	rep
   eseg movs				! copy video words
	shr	si, #1
	shr	di, #1			! word addresses
	test	dx, dx
	jnz	vvc_uploop		! again?
	jmp	vvc_done
vvc_down:
	std				! direction is down
	add	si, dx			! start copying at the top
	dec	si
	add	di, dx
	dec	di
vvc_downloop:
	and	si, _vid_mask		! wrap addresses
	and	di, _vid_mask
	mov	cx, dx			! one chunk to copy
	lea	ax, 1(si)
	cmp	cx, ax
	jbe	0f
	mov	cx, ax			! cx = min(cx, si + 1)
0:	lea	ax, 1(di)
	cmp	cx, ax
	jbe	0f
	mov	cx, ax			! cx = min(cx, di + 1)
0:	sub	dx, cx			! count -= cx
	shl	si, #1
	shl	di, #1			! byte addresses
	rep
   eseg	movs				! copy video words
	shr	si, #1
	shr	di, #1			! word addresses
	test	dx, dx
	jnz	vvc_downloop		! again?
	cld				! C compiler expect up
	!jmp	vvc_done
vvc_done:
	pop	es
	pop	di
	pop	si
	ret


!*===========================================================================*
!*			      level0					     *
!*===========================================================================*
! PUBLIC void level0(void (*func)(void))
! Not very interesting in real mode, see p_level0.
!
_level0:
	mov	bx, sp
	jmp	@2(bx)


!*===========================================================================*
!*				klib_init_prot				     *
!*===========================================================================*
! PUBLIC void klib_init_prot();
! Initialize klib for protected mode by patching some real mode functions
! at their starts to jump to their protected mode equivalents, according to
! the patch table.  Saves a lot of tests on the "protected_mode" variable.
! Note that this function must be run in real mode, for it writes the code
! segment.  (One otherwise has to set up a descriptor, etc, etc.)

klib_init_prot:
	mov	si,#patch_table
kip_next:
	lods			! original function
	mov	bx,ax
  cseg	movb	(bx),#JMP_OPCODE ! overwrite start of function by a long jump
	lods			! new function - target of jump
	sub	ax,bx		! relative jump
	sub	ax,#3		! adjust by length of jump instruction
  cseg	mov	1(bx),ax	! set address
	cmp	si,#end_patch_table ! end of table?
	jb	kip_next
kip_done:
	ret


!*===========================================================================*
!*			variants for protected mode			     *
!*===========================================================================*
! Some routines are different in protected mode.
! The only essential difference is the handling of segment registers.
! One complication is that the method of building segment descriptors is not
! reentrant, so the protected mode versions must not be called by interrupt
! handlers.


!*===========================================================================*
!*				p_cp_mess				     *
!*===========================================================================*
! The real mode version attempts to be efficient by passing raw segments but
! that just gets in the way here.

p_cp_mess:
	cld
	pop	dx
	pop	bx		! proc
	pop	cx		! source clicks
	pop	ax		! source offset
#if CLICK_SHIFT != HCLICK_SHIFT + 4
#error /* the only click size supported is 256, to avoid slow shifts here */
#endif
	addb	ah,cl		! calculate source offset
	adcb	ch,#0		! and put in base of source descriptor
	mov	_gdt+DS_286_OFFSET+DESC_BASE,ax
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,ch
	pop	cx		! destination clicks
	pop	ax		! destination offset
	addb	ah,cl		! calculate destination offset
	adcb	ch,#0		! and put in base of destination descriptor
	mov	_gdt+ES_286_OFFSET+DESC_BASE,ax
	movb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE,ch
	sub	sp,#2+2+2+2+2

	push	ds
	push	es
	mov	ax,#DS_286_SELECTOR
	mov	ds,ax
	mov	ax,#ES_286_SELECTOR
	mov	es,ax

  eseg	mov	0,bx		! proc no. of sender from arg, not msg
	mov	ax,si
	mov	bx,di
	mov	si,#2		! src offset is now 2 relative to start of seg
	mov	di,si		! and destination offset
	mov	cx,#Msize-1	! word count
	rep
	movs
	mov	di,bx
	mov	si,ax
	pop	es
	pop	ds
	jmp	(dx)


!*===========================================================================*
!*				p_portio_setup				     *
!*===========================================================================*
! The port_read, port_write, etc. functions need a setup routine that uses
! a segment descriptor.
p_portio_setup:
	mov	ax,4+2(bp)	! source/destination address in dx:ax
	mov	dx,4+2+2(bp)
	mov	_gdt+DS_286_OFFSET+DESC_BASE,ax
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,dl
	xor	bx,bx		! bx = 0 = start of segment
	mov	ax,#DS_286_SELECTOR ! ax = segment selector
	mov	cx,4+2+4(bp)	! count in bytes
	mov	dx,4(bp)	! port to read from
	cld			! direction is UP
	ret


!*===========================================================================*
!*				p_phys_copy				     *
!*===========================================================================*
p_phys_copy:
	cld
	pop	dx
	pop	_gdt+DS_286_OFFSET+DESC_BASE
	pop	ax		! pop source into base of source descriptor
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,al
	pop	_gdt+ES_286_OFFSET+DESC_BASE
	pop	ax		! pop destination into base of dst descriptor
	movb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE,al
	pop	cx		! byte count in bx:cx
	pop	bx
	sub	sp,#4+4+4

	push	di
	push	si
	push	es
	push	ds
	sub	si,si		! src offset is now 0 relative to start of seg
	mov	di,si		! and destination offset
	jmp	ppc_next

! It is too much trouble to align the segment bases, so word alignment is hard.
! Avoiding the book-keeping for alignment may be good anyway.

ppc_large:
	push	cx
	mov	cx,#0x8000	! copy a large chunk of this many words
	rep
	movs
	pop	cx
	dec	bx
	pop	ds		! update the descriptors
	incb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE
	incb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE
	push	ds
ppc_next:
	mov	ax,#DS_286_SELECTOR	! (re)load the selectors
	mov	ds,ax
	mov	ax,#ES_286_SELECTOR
	mov	es,ax
	test	bx,bx
	jnz	ppc_large

	shr	cx,#1		! word count
	rep
	movs			! move any leftover words
	rcl	cx,#1		! restore old bit 0
	rep
	movb			! move any leftover byte
	pop	ds
	pop	es
	pop	si
	pop	di
	jmp	(dx)

!*===========================================================================*
!*				p_reset					     *
!*===========================================================================*
! Reset the system by loading IDT with offset 0 and interrupting.

p_reset:
	lidt	idt_zero
	int	3		! anything goes, the 286 will not like it


!*===========================================================================*
!*				p_level0				     *
!*===========================================================================*
! PUBLIC void level0(void (*func)(void))
! Call a function at permission level 0.  This allows kernel tasks to do
! things that are only possible at the most privileged CPU level.
!
p_level0:
	mov	bx, sp
	mov	ax, 2(bx)
	mov	_level0_func, ax
	int	LEVEL0_VECTOR
	ret


!*===========================================================================*
!*				data					     *
!*===========================================================================*
	.data
patch_table:			! pairs (old function, new function)
#if ENABLE_BIOS_WINI
	.data2	_bios13, p_bios13
#endif
	.data2	_cp_mess, p_cp_mess
	.data2	_phys_copy, p_phys_copy
	.data2	portio_setup, p_portio_setup
	.data2	_reset, p_reset
	.data2	_level0, p_level0
	.data2	_restart, p_restart	! in mpx file
	.data2	save, p_save	! in mpx file
end_patch_table:		! end of table

idt_vectors:			! limit and base of real mode interrupt vectors
	.data2	0x3FF
idt_zero:			! zero limit IDT to cause a processor shutdown
	.data2	0, 0, 0

	.bss
save_sp:			! place to put sp when switching to real mode
	.space	2
msw:				! saved real mode machine status word
	.space	2
