#
#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	_int86		! make an 8086 interrupt call
.define real2prot	! switch from real to protected mode
.define prot2real	! switch from protected to real mode
.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	_phys_insw	! transfer data from (disk controller) port to memory
.define	_phys_insb	! likewise byte by byte
.define	_phys_outsw	! transfer data from memory to (disk controller) port
.define	_phys_outsb	! likewise byte by byte
.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 */

! Exported variables
.extern kernel_cs

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

_monitor:
	call	prot2real		! switch to real mode
	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


!*===========================================================================*
!*				int86					     *
!*===========================================================================*
! PUBLIC void int86();
_int86:				! make an 8086 interrupt call.
	push	bp
	push	si
	push	di		! save C variable registers
	pushf			! save flags

	call	int86		! make the actual call

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

! Do an 8086 interrupt from protected mode
p_int86:
	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]	! keep the clock ticking
	outb	INT_CTLMASK		! enable all unused IRQs and vv.
	movb	al, ah
	outb	INT2_CTLMASK

	call	prot2real		! switch to real mode

	xor	ax, ax
	mov	es, ax			! vector & BIOS data segments
   eseg	mov	0x046C, ax		! clear BIOS clock counter
   eseg	mov	0x046E, ax

	sti				! enable interrupts
	call	int86			! make the actual call
	cli				! disable interrupts

	xor	ax, ax
	mov	es, ax
   eseg	mov	ax, 0x046C
	add	_lost_ticks, ax		! record lost clock ticks

	call	real2prot		! back to protected mode

	pop	ax			! restore interrupt masks
	outb	INT_CTLMASK
	movb	al, ah
	outb	INT2_CTLMASK

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

int86:
	mov	bp, #_reg86	! address of parameter block
	movb	al, #0xCD	! INT instruction
	movb	ah, (bp)	! Interrupt number?
	testb	ah, ah
	jnz	0f		! nonzero if INT, otherwise far call
	push	cs
	push	#intret+2	! far return address
	push	6(bp)
	push	4(bp)		! far driver address
	mov	ax, #0x90CB	! RETF; NOP
0: cseg	mov	intret, ax	! patch `INT n` or `RETF; NOP` into code
	jmp	.+2		! clear instruction queue

	mov	ds, 8(bp)	! Load parameters
	mov	es, 10(bp)
	mov	ax, 12(bp)	! (sorry, only ax set, not eax)
	mov	bx, 16(bp)
	mov	cx, 20(bp)
	mov	dx, 24(bp)
	mov	si, 28(bp)
	mov	di, 32(bp)
	mov	bp, 36(bp)

intret:	int	0xFF		! do the interrupt or far call

	push	bp
	pushf
	mov	bp, #_reg86	! address of parameter block
	pop	(bp)		! eflags
	mov	8(bp), ds
	mov	10(bp), es
	mov	12(bp), ax
	mov	16(bp), bx
	mov	20(bp), cx
	mov	24(bp), dx
	mov	28(bp), si
	mov	32(bp), di
	pop	36(bp)		! bp

	mov	ax, ss
	mov	ds, ax		! restore ds and es
	mov	es, ax
	ret


!*===========================================================================*
!*				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, #0x02
	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:
	xor	ax, ax
	mov	_gdt+ES_286_OFFSET+DESC_BASE, ax
	movb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE, al
	movb	_gdt+ES_286_OFFSET+DESC_BASE_HIGH, al
	mov	ax, #ES_286_SELECTOR
	mov	es, ax			! BIOS data segment
  eseg	mov	0x0467, #real		! set return from shutdown address
  cseg	mov	ax, kernel_cs
  eseg	mov	0x0469, 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

	xorb	ah, ah
	!jmp	gate_A20		! disable the A20 address line

! Enable (ah = 0x02) or disable (ah = 0x00) the A20 address line.
gate_A20:
	cmp	_ps_mca, #0	! PS/2 bus?
	jnz	gate_PS_A20
	call	kb_wait
	movb	al, #0xD1	! Tell keyboard that a command is coming
	outb	0x64
	call	kb_wait
	movb	al, #0xDD	! 0xDD = A20 disable code if ah = 0x00
	orb	al, ah		! 0xDF = A20 enable code if ah = 0x02
	outb	0x60
	call	kb_wait
	movb	al, #0xFF	! Pulse output port
	outb	0x64
	call	kb_wait		! Wait for the A20 line to settle down
	ret
kb_wait:
	inb	0x64
	testb	al, #0x02	! Keyboard input buffer full?
	jnz	kb_wait		! If so, wait
	ret

gate_PS_A20:		! The PS/2 can twiddle A20 using port A
	inb	0x92		! Read port A
	andb	al, #0xFD
	orb	al, ah		! Set A20 bit to the required state
	outb	0x92		! Write port A
	jmp	.+2		! Small delay
A20ok:	inb	0x92		! Check port A
	andb	al, #0x02
	cmpb	al, ah		! A20 line settled down to the new state?
	jne	A20ok		! If not then wait
	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


!*===========================================================================*
!*				phys_insw				     *
!*===========================================================================*
! PUBLIC void phys_insw(Port_t port, phys_bytes buf, size_t count);
! Input an array from an I/O port.  Absolute address version of insw().

_phys_insw:
	call	portio_intro
	mov	es, bx			! destination segment
	mov	di, ax			! destination offset
	shr	cx, #1			! word count
   rep	ins				! input many words
	jmp	portio_return

portio_intro:
	pop	ax			! hold return address
	push	bp			! create stack frame
	mov	bp, sp
	push	si
	push	di
	push	ax			! retore return address
	mov	dx, 4(bp)		! port to do I/O
	mov	ax, 6(bp)		! source/destination address in bx:ax
	mov	bx, 8(bp)
	mov	cx, 10(bp)		! count in bytes
	cld				! direction is UP
portio_setup:
	push	cx			! segment/offset setup
	movb	ch, bl
	mov	bx, ax
	and	ax, #0x000F		! ax = offset = address % HCLICK_SIZE
	movb	cl, #4
	shr	bx, cl
	shlb	ch, cl
	orb	bh, ch			! bx = segment = address / HCLICK_SIZE
	pop	cx
	ret

portio_return:
	mov	ax, ss			! restore segment registers
	mov	ds, ax
	mov	es, ax
	pop	di			! unwind stack frame
	pop	si
	pop	bp
	ret


!*===========================================================================*
!*				phys_insb				     *
!*===========================================================================*
! PUBLIC void phys_insb(Port_t port, phys_bytes buf, size_t count);
! Input an array from an I/O port.  Absolute address version of insb().
! Note: The 8086 doesn't have string I/O instructions, so a loop is used.

_phys_insb:
	call	portio_intro
	mov	es, bx			! destination segment
	mov	di, ax			! destination offset
	jcxz	1f
0:	inb	dx			! input 1 byte
	stosb				! write 1 byte
	loop	0b			! many times
1:
	jmp	portio_return


!*===========================================================================*
!*				phys_outsw				     *
!*===========================================================================*
! PUBLIC void phys_outsw(Port_t port, phys_bytes buf, size_t count);
! Output an array to an I/O port.  Absolute address version of outsw().

_phys_outsw:
	call	portio_intro
	mov	ds, bx			! source segment
	mov	si, ax			! source offset
	shr	cx, #1			! word count
   rep	outs				! output many words
	jmp	portio_return


!*===========================================================================*
!*				phys_outsb				     *
!*===========================================================================*
! PUBLIC void phys_outsb(Port_t port, phys_bytes buf, size_t count);
! Output an array to an I/O port.  Absolute address version of outsb().

_phys_outsb:
	call	portio_intro
	mov	ds, bx			! source segment
	mov	si, ax			! source offset
	jcxz	1f
0:	lodsb				! read 1 byte
	outb	dx			! output 1 byte
	loop	0b			! many times
1:
	jmp	portio_return


!*==========================================================================*
!*				enable_irq				    *
!*==========================================================================*/
! PUBLIC void enable_irq(irq_hook_t *hook)
! Enable an interrupt request line by clearing an 8259 bit.
! Equivalent code for irq < 8:
!   if ((irq_actids[hook->irq] &= ~hook->id) == 0)
!	outb(INT_CTLMASK, inb(INT_CTLMASK) & ~(1 << irq));

_enable_irq:
	push	bp
	mov	bp, sp
	pushf
	cli
	mov	bx, 4(bp)		! hook
	mov	cx, 4(bx)		! irq
	mov	ax, 6(bx)		! id bit
	not	ax
	mov	bx, cx
	add	bx, bx
	and	_irq_actids(bx), ax	! clear this id bit
	jnz	en_done			! still masked by other handlers?
	movb	ah, #~1
	rolb	ah, cl			! ah = ~(1 << (irq % 8))
	mov	dx, #INT_CTLMASK	! enable irq < 8 at the master 8259
	cmpb	cl, #8
	jb	0f
	mov	dx, #INT2_CTLMASK	! enable irq >= 8 at the slave 8259
0:	inb	dx
	andb	al, ah
	outb	dx			! clear bit at the 8259
en_done:popf
	leave
	ret


!*==========================================================================*
!*				disable_irq				    *
!*==========================================================================*/
! PUBLIC int disable_irq(irq_hook_t *hook)
! Disable an interrupt request line by setting an 8259 bit.
! Equivalent code for irq < 8:
!   irq_actids[hook->irq] |= hook->id;
!   outb(INT_CTLMASK, inb(INT_CTLMASK) | (1 << irq));
! Returns true iff the interrupt was not already disabled.

_disable_irq:
	push	bp
	mov	bp, sp
	pushf
	cli
	mov	bx, 4(bp)		! hook
	mov	cx, 4(bx)		! irq
	mov	ax, 6(bx)		! id bit
	pushf
	cli
	mov	bx, cx
	add	bx, bx
	or	_irq_actids(bx), ax	! set this id bit
	movb	ah, #1
	rolb	ah, cl			! ah = (1 << (irq % 8))
	mov	dx, #INT_CTLMASK	! disable irq < 8 at the master 8259
	cmpb	cl, #8
	jb	0f
	mov	dx, #INT2_CTLMASK	! disable irq >= 8 at the slave 8259
0:	inb	dx
	testb	al, ah
	jnz	dis_already		! already disabled?
	orb	al, ah
	outb	dx			! set bit at the 8259
	mov	ax, #1			! disabled by this function
	popf
	leave
	ret
dis_already:
	xor	ax, ax			! already disabled
	popf
	leave
	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 at F000:FFF0.

_reset:
	jmpf	0xFFF0,0xF000


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

_mem_vid_copy:
	push	bp
	mov	bp, sp
	push	si
	push	di
	push	es
	mov	si, 4(bp)		! source
	mov	di, 6(bp)		! destination
	mov	dx, 8(bp)		! count
	mov	es, _vid_seg		! segment containing video memory
	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
	add	di, _vid_off		! in video memory
	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:
	sub	di, _vid_off
	shr	di, #1			! back to a word address
	test	dx, dx
	jnz	mvc_loop
mvc_done:
	pop	es
	pop	di
	pop	si
	pop	bp
	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.

_vid_vid_copy:
	push	bp
	mov	bp, sp
	push	si
	push	di
	push	es
	mov	si, 4(bp)		! source
	mov	di, 6(bp)		! destination
	mov	dx, 8(bp)		! count
	mov	es, _vid_seg		! segment containing video memory
	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
	call	vvc_copy
	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
	call	vvc_copy
	test	dx, dx
	jnz	vvc_downloop		! again?
	cld				! C compiler expect up
	!jmp	vvc_done
vvc_done:
	pop	es
	pop	di
	pop	si
	pop	bp
	ret

! Copy video words.  (Inner code of both the up and downcopying loop.)
vvc_copy:
	shl	si, #1
	shl	di, #1			! byte addresses
	add	si, _vid_off
	add	di, _vid_off		! in video memory
	push	ds			! must set ds here, 8086 can't do
	mov	ds, _vid_seg		! 'rep eseg movs' with interrupts on
	rep
	movs				! copy video words
	pop	ds
	sub	si, _vid_off
	sub	di, _vid_off
	shr	si, #1
	shr	di, #1			! back to word addresses
	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:
	mov	bx, sp		! bx -> arguments
	push	si
	push	di
	push	ds
	push	es

	mov	ax, 4(bx)	! Compute source descriptor base
	mov	dx, ax
	shl	ax, #CLICK_SHIFT
	shr	dx, #16-CLICK_SHIFT	! dx:ax = src_clicks * CLICK_SIZE
	add	ax, 6(bx)
	adc	dx, #0			! dx:ax += src_offset
	mov	_gdt+DS_286_OFFSET+DESC_BASE, ax
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE, dl
	movb	_gdt+DS_286_OFFSET+DESC_BASE_HIGH, dh

	mov	ax, 8(bx)	! Compute destination descriptor base
	mov	dx, ax
	shl	ax, #CLICK_SHIFT
	shr	dx, #16-CLICK_SHIFT	! dx:ax = dst_clicks * CLICK_SIZE
	add	ax, 10(bx)
	adc	dx, #0			! dx:ax += dst_offset
	mov	_gdt+ES_286_OFFSET+DESC_BASE, ax
	movb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE, dl
	movb	_gdt+ES_286_OFFSET+DESC_BASE_HIGH, dh

	mov	bx, 2(bx)		! proc no
	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	si, #2		! src offset is now 2 relative to start of seg
	mov	di, si		! and destination offset
	mov	cx, #Msize-1	! word count
	cld			! direction is up
	rep
	movs			! copy message (except first word)

	pop	es
	pop	ds
	pop	di
	pop	si
	ret


!*===========================================================================*
!*				p_portio_setup				     *
!*===========================================================================*
! The phys_insw, phys_outsw, etc. functions need an address setup routine that
! uses a segment descriptor.
p_portio_setup:
	mov	_gdt+DS_286_OFFSET+DESC_BASE, ax
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE, bl
	movb	_gdt+DS_286_OFFSET+DESC_BASE_HIGH, bh
	xor	ax, ax			! ax = 0 = start of segment
	mov	bx, #DS_286_SELECTOR	! bx = segment selector
	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
	movb	_gdt+DS_286_OFFSET+DESC_BASE_HIGH,ah
	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
	movb	_gdt+ES_286_OFFSET+DESC_BASE_HIGH,ah
	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
	addb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,#1
	adcb	_gdt+DS_286_OFFSET+DESC_BASE_HIGH,#0
	addb	_gdt+ES_286_OFFSET+DESC_BASE_MIDDLE,#1
	adcb	_gdt+ES_286_OFFSET+DESC_BASE_HIGH,#0
	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)
	.data2	_int86, p_int86
	.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
