| This file contains a number of assembly code utility routines needed by the
| kernel.  They are:
|
|   phys_copy:	copies data from anywhere to anywhere in memory
|   cp_mess:	copies messages from source to destination
|   port_out:	outputs data on an I/O port
|   port_in:	inputs data from an I/O port
|   lock:	disable interrupts
|   unlock:	enable interrupts
|   restore:	restore interrupts (enable/disabled) as they were before lock()
|   build_sig:	build 4 word structure pushed onto stack for signals
|   csv:	procedure prolog to save the registers
|   cret:	procedure epilog to restore the registers
|   get_chrome:	returns 0 is display is monochrome, 1 if it is color
|   vid_copy:	copy data to video ram (on color display during retrace only)
|   get_byte:	reads a byte from a user program and returns it as value
|   reboot:	reboot for CTRL-ALT-DEL
|   wreboot:	wait for character then reboot 
|   dma_read:	transfer data between HD controller and memory
|   dma_write:	transfer data between memory and HD controller

| The following procedures are defined in this file and called from outside it.
.globl _phys_copy, _cp_mess, _port_out, _port_in, _lock, _unlock, _restore
.globl _build_sig, csv, cret, _get_chrome, _vid_copy, _get_byte, _reboot
.globl _wreboot, _dma_read, _dma_write

| The following external procedure is called in this file.
.globl _panic

| Variables and data structures
.globl _color, _cur_proc, _proc_ptr, splimit, _vec_table, _vid_mask


|*===========================================================================*
|*				phys_copy				     *
|*===========================================================================*
| This routine copies a block of physical memory.  It is called by:
|    phys_copy( (long) source, (long) destination, (long) bytecount)

_phys_copy:
	pushf			| save flags
	cli			| disable interrupts
	push bp			| save the registers
	push ax			| save ax
	push bx			| save bx
	push cx			| save cx
	push dx			| save dx
	push si			| save si
	push di			| save di
	push ds			| save ds
	push es			| save es
	mov bp,sp		| set bp to point to saved es

  L0:	mov ax,28(bp)		| ax = high-order word of 32-bit destination
	mov di,26(bp)		| di = low-order word of 32-bit destination
	mov cx,*4		| start extracting click number from dest
  L1:	rcr ax,*1		| click number is destination address / 16
	rcr di,*1		| it is used in segment register for copy
	loop L1			| 4 bits of high-order word are used
	mov es,di		| es = destination click

	mov ax,24(bp)		| ax = high-order word of 32-bit source
	mov si,22(bp)		| si = low-order word of 32-bit source
	mov cx,*4		| start extracting click number from source
  L2:	rcr ax,*1		| click number is source address / 16
	rcr si,*1		| it is used in segment register for copy
	loop L2			| 4 bits of high-order word are used
	mov ds,si		| ds = source click

	mov di,26(bp)		| di = low-order word of dest address
	and di,*0x000F		| di = offset from paragraph # in es
	mov si,22(bp)		| si = low-order word of source address
	and si,*0x000F		| si = offset from paragraph # in ds

	mov dx,32(bp)		| dx = high-order word of byte count
	mov cx,30(bp)		| cx = low-order word of byte count

	test cx,#0x8000		| if bytes >= 32768, only do 32768 
	jnz L3			| per iteration
	test dx,#0xFFFF		| check high-order 17 bits to see if bytes
	jnz L3			| if bytes >= 32768 then go to L3
	jmp L4			| if bytes < 32768 then go to L4
  L3:	mov cx,#0x8000		| 0x8000 is unsigned 32768
  L4:	mov ax,cx		| save actual count used in ax; needed later

	test cx,*0x0001		| should we copy a byte or a word at a time?
	jz L5			| jump if even
	rep			| copy 1 byte at a time
	movb			| byte copy
	jmp L6			| check for more bytes

  L5:	shr cx,*1		| word copy
	rep			| copy 1 word at a time
	movw			| word copy

  L6:	mov dx,32(bp)		| decr count, incr src & dst, iterate if needed
	mov cx,30(bp)		| dx || cx is 32-bit byte count
	xor bx,bx		| bx || ax is 32-bit actual count used
	sub cx,ax		| compute bytes - actual count
	sbb dx,bx		| dx || cx is # bytes not yet processed
	or cx,cx		| see if it is 0
	jnz L7			| if more bytes then go to L7
	or dx,dx		| keep testing
	jnz L7			| if loop done, fall through

	pop es			| restore all the saved registers
	pop ds			| restore ds
	pop di			| restore di
	pop si			| restore si
	pop dx			| restore dx
	pop cx			| restore cx
	pop bx			| restore bx
	pop ax			| restore ax
	pop bp			| restore bp
	popf			| restore flags
	ret			| return to caller

L7:	mov 32(bp),dx		| store decremented byte count back in mem
	mov 30(bp),cx		| as a long
	add 26(bp),ax		| increment destination
	adc 28(bp),bx		| carry from low-order word
	add 22(bp),ax		| increment source
	adc 24(bp),bx		| carry from low-order word
	jmp L0			| start next iteration


|*===========================================================================*
|*				cp_mess					     *
|*===========================================================================*
| This routine is 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.
| It is called by:
|    cp_mess(src, src_clicks, src_offset, dst_clicks, dst_offset)
| where all 5 parameters are shorts (16-bits).
|
| Note that the message size, 'Msize' is in WORDS (not bytes) and must be set
| correctly.  Changing the definition of message the type file and not changing
| it here will lead to total disaster.
| This routine destroys ax.  It preserves the other registers.

Msize = 12			| size of a message in 16-bit words
_cp_mess:
	push bp			| save bp
	push es			| save es
	push ds			| save ds
	mov bp,sp		| index off bp because machine can't use sp
	pushf			| save flags
	cli			| disable interrupts
	push cx			| save cx
	push si			| save si
	push di			| save di

	mov ax,8(bp)		| ax = process number of sender
	mov di,16(bp)		| di = offset of destination buffer
	mov es,14(bp)		| es = clicks of destination
	mov si,12(bp)		| si = offset of source message
	mov ds,10(bp)		| ds = clicks of source message
	seg es			| segment override prefix
  	mov (di),ax		| copy sender's process number to dest message
	add si,*2		| don't copy first word
	add di,*2		| don't copy first word
	mov cx,*Msize-1		| remember, first word doesn't count
	rep			| iterate cx times to copy 11 words
	movw			| copy the message

	pop di			| restore di
	pop si			| restore si
	pop cx			| restore cs
	popf			| restore flags
	pop ds			| restore ds
	pop es			| restore es
	pop bp			| restore bp
	ret			| that's all folks!


|*===========================================================================*
|*				port_out				     *
|*===========================================================================*
| port_out(port, value) writes 'value' on the I/O port 'port'.

_port_out:
	push bx			| save bx
	mov bx,sp		| index off bx
	push ax			| save ax
	push dx			| save dx
	mov dx,4(bx)		| dx = port
	mov ax,6(bx)		| ax = value
	out			| output 1 byte
	pop dx			| restore dx
	pop ax			| restore ax
	pop bx			| restore bx
	ret			| return to caller


|*===========================================================================*
|*				port_in					     *
|*===========================================================================*
| port_in(port, &value) reads from port 'port' and puts the result in 'value'.
_port_in:
	push bx			| save bx
	mov bx,sp		| index off bx
	push ax			| save ax
	push dx			| save dx
	mov dx,4(bx)		| dx = port
	in			| input 1 byte
	xorb ah,ah		| clear ah
	mov bx,6(bx)		| fetch address where byte is to go
	mov (bx),ax		| return byte to caller in param
	pop dx			| restore dx
	pop ax			| restore ax
	pop bx			| restore bx
	ret			| return to caller


|*===========================================================================*
|*				lock					     *
|*===========================================================================*
| Disable CPU interrupts.
_lock:  
	pushf			| save flags on stack
	cli			| disable interrupts
	pop lockvar		| save flags for possible restoration later
	ret			| return to caller


|*===========================================================================*
|*				unlock					     *
|*===========================================================================*
| Enable CPU interrupts.
_unlock:
	sti			| enable interrupts
	ret			| return to caller


|*===========================================================================*
|*				restore					     *
|*===========================================================================*
| Restore enable/disable bit to the value it had before last lock.
_restore:
	push lockvar		| push flags as they were before previous lock
	popf			| restore flags
	ret			| return to caller


|*===========================================================================*
|*				build_sig				     *
|*===========================================================================*
|* Build a structure that is pushed onto the stack for signals.  It contains
|* pc, psw, etc., and is machine dependent. The format is the same as generated
|* by hardware interrupts, except that after the "interrupt", the signal number
|* is also pushed.  The signal processing routine within the user space first
|* pops the signal number, to see which function to call.  Then it calls the
|* function.  Finally, when the function returns to the low-level signal
|* handling routine, control is passed back to where it was prior to the signal
|* by executing a return-from-interrupt instruction, hence the need for using
|* the hardware generated interrupt format on the stack.  The call is:
|*     build_sig(sig_stuff, rp, sig)

| Offsets within proc table
PC    = 24
csreg = 18
PSW   = 28

_build_sig:
	push bp			| save bp
	mov bp,sp		| set bp to sp for accessing params
	push bx			| save bx
	push si			| save si
	mov bx,4(bp)		| bx points to sig_stuff
	mov si,6(bp)		| si points to proc table entry
	mov ax,8(bp)		| ax = signal number
	mov (bx),ax		| put signal number in sig_stuff
	mov ax,PC(si)		| ax = signalled process' PC
	mov 2(bx),ax		| put pc in sig_stuff
	mov ax,csreg(si)	| ax = signalled process' cs
	mov 4(bx),ax		| put cs in sig_stuff
	mov ax,PSW(si)		| ax = signalled process' PSW
	mov 6(bx),ax		| put psw in sig_stuff
	pop si			| restore si
	pop bx			| restore bx
	pop bp			| restore bp
	ret			| return to caller


|*===========================================================================*
|*				csv & cret				     *
|*===========================================================================*
| This version of csv replaces the standard one.  It checks for stack overflow
| within the kernel in a simpler way than is usually done. cret is standard.
csv:
	pop bx			| bx = return address
	push bp			| stack old frame pointer
	mov bp,sp		| set new frame pointer to sp
	push di			| save di
	push si			| save si
	sub sp,ax		| ax = # bytes of local variables
	cmp sp,splimit		| has kernel stack grown too large
	jbe csv.1		| if sp is too low, panic
	jmp (bx)		| normal return: copy bx to program counter

csv.1:
	mov splimit,#0		| prevent call to panic from aborting in csv
	mov bx,_proc_ptr	| update rp->p_splimit
	mov 50(bx),#0		| rp->sp_limit = 0
	push _cur_proc		| task number
	mov ax,#stkoverrun	| stack overran the kernel stack area
	push ax			| push first parameter
	call _panic		| call is: panic(stkoverrun, cur_proc)
	jmp csv.1		| this should not be necessary


cret:
	lea	sp,*-4(bp)	| set sp to point to saved si
	pop	si		| restore saved si
	pop	di		| restore saved di
	pop	bp		| restore bp
	ret			| end of procedure

|*===========================================================================*
|*				get_chrome				     *
|*===========================================================================*
| This routine calls the BIOS to find out if the display is monochrome or 
| color.  The drivers are different, as are the video ram addresses, so we
| need to know.
_get_chrome:
	int 0x11		| call the BIOS to get equipment type
	andb al,#0x30		| isolate color/mono field
	cmpb al,*0x30		| 0x30 is monochrome
	je getchr1		| if monochrome then go to getchr1
	mov ax,#1		| color = 1
	ret			| color return
getchr1: xor ax,ax		| mono = 0
	ret			| monochrome return

|*===========================================================================*
|*				dma_read				     *
|*===========================================================================*
_dma_read:
	push	bp
	mov	bp,sp
	push	cx
	push	dx
	push	di
	push	es
	mov	cx,#256		| transfer 256 words
	mov	dx,#0x1F0	| from/to port 1f0
	cld
	mov	es,4(bp)	| segment in es
	mov	di,6(bp)	| offset in di
	.byte	0xF3, 0x6D	| opcode for 'rep insw'
	pop	es
	pop	di
	pop	dx
	pop	dx
	mov	sp,bp
	pop	bp
	ret

|*===========================================================================*
|*				dma_write				     *
|*===========================================================================*
_dma_write:
	push	bp
	mov	bp,sp
	push	cx
	push	dx
	push	si
	push	ds
	mov	cx,#256		| transfer 256 words
	mov	dx,#0x1F0	| from/to port 1f0
	cld
	mov	ds,4(bp)	| segment in ds
	mov	si,6(bp)	| offset in si
	.byte	0xF3, 0x6F	| opcode for 'rep outsw'
	pop	ds
	pop	si
	pop	dx
	pop	dx
	mov	sp,bp
	pop	bp
	ret

|*===========================================================================*
|*				vid_copy				     *
|*===========================================================================*
| This routine takes a string of (character, attribute) pairs and writes them
| onto the screen.  For a color display, the writing only takes places during
| the vertical retrace interval, to avoid displaying garbage on the screen.
| The call is:
|     vid_copy(buffer, videobase, offset, words)
| where
|     'buffer'    is a pointer to the (character, attribute) pairs
|     'videobase' is 0xB800 for color and 0xB000 for monochrome displays
|     'offset'    tells where within video ram to copy the data
|     'words'     tells how many words to copy
| if buffer is zero, the fill char (BLANK) is used

BLANK = 0x0700			| controls color of cursor on blank screen

_vid_copy:
	push bp			| we need bp to access the parameters
	mov bp,sp		| set bp to sp for indexing
	push si			| save the registers
	push di			| save di
	push bx			| save bx
	push cx			| save cx
	push dx			| save dx
	push es			| save es
vid.0:	mov si,4(bp)		| si = pointer to data to be copied
	mov di,8(bp)		| di = offset within video ram
	and di,_vid_mask	| only 4K or 16K counts
	mov cx,10(bp)		| cx = word count for copy loop
	mov dx,#0x3DA		| prepare to see if color display is retracing

	mov bx,di		| see if copy will run off end of video ram
	add bx,cx		| compute where copy ends
	add bx,cx		| bx = last character copied + 1
	sub bx,_vid_mask	| bx = # characters beyond end of video ram
	sub bx,#1		| note: dec bx doesn't set flags properly
	jle vid.1		| jump if no overrun
	sar bx,#1		| bx = # words that don't fit in video ram
	sub cx,bx		| reduce count by overrun
	mov tmp,cx		| save actual count used for later

vid.1:	test _color,*1		| skip vertical retrace test if display is mono
	jz vid.4		| if monochrome then go to vid.2

|vid.2:	in			| with a color display, you can only copy to
|	test al,*010		| the video ram during vertical retrace, so
|	jnz vid.2		| wait for start of retrace period.  Bit 3 of
vid.3:	in			| 0x3DA is set during retrace.  First wait
	testb al,*010		| until it is off (no retrace), then wait
	jz vid.3		| until it comes on (start of retrace)

vid.4:	pushf			| copying may now start; save flags
	cli			| interrupts just get in the way: disable them
	mov es,6(bp)		| load es now: int routines may ruin it

	cmp si,#0		| si = 0 means blank the screen
	je vid.7		| jump for blanking
	lock			| this is a trick for the IBM PC simulator only
	inc vidlock		| 'lock' indicates a video ram access
	rep			| this is the copy loop
	movw			| ditto

vid.5:	popf			| restore flags
	cmp bx,#0		| if bx < 0, then no overrun and we are done
	jle vid.6		| jump if everything fit
	mov 10(bp),bx		| set up residual count
	mov 8(bp),#0		| start copying at base of video ram
	cmp 4(bp),#0		| NIL_PTR means store blanks
	je vid.0		| go do it
	mov si,tmp		| si = count of words copied
	add si,si		| si = count of bytes copied
	add 4(bp),si		| increment buffer pointer
	jmp vid.0		| go copy some more

vid.6:	pop es			| restore registers
	pop dx			| restore dx
	pop cx			| restore cx
	pop bx			| restore bx
	pop di			| restore di
	pop si			| restore si
	pop bp			| restore bp
	ret			| return to caller

vid.7:	mov ax,#BLANK		| ax = blanking character
	rep			| copy loop
	stow			| blank screen
	jmp vid.5		| done

|*===========================================================================*
|*				get_byte				     *
|*===========================================================================*
| This routine is used to fetch a byte from anywhere in memory.
| The call is:
|     c = get_byte(seg, off)
| where
|     'seg' is the value to put in es
|     'off' is the offset from the es value
_get_byte:
	push bp			| save bp
	mov bp,sp		| we need to access parameters
	push es			| save es
	mov es,4(bp)		| load es with segment value
	mov bx,6(bp)		| load bx with offset from segment
	seg es			| go get the byte
	movb al,(bx)		| al = byte
	xorb ah,ah		| ax = byte
	pop es			| restore es
	pop bp			| restore bp
	ret			| return to caller




|*===========================================================================*
|*				reboot & wreboot			     *
|*===========================================================================*
| This code reboots the PC

_reboot:
	cli			| disable interrupts
	mov ax,#0x20		| re-enable interrupt controller
	out 0x20
	call resvec		| restore the vectors in low core
	mov ax,#0x40
	mov ds,ax
	mov ax,#0x1234
	mov 0x72,ax
	mov ax,#0xFFFF
	mov ds,ax
	mov ax,3
	push ax
	mov ax,1
	push ax
	reti

_wreboot:
	cli			| disable interrupts
	mov ax,#0x20		| re-enable interrupt controller
	out 0x20
	call resvec		| restore the vectors in low core
	xor ax,ax		| wait for character before continuing
	int 0x16		| get char
	mov ax,#0x40
	mov ds,ax
	mov ax,#0x1234
	mov 0x72,ax
	mov ax,#0xFFFF
	mov ds,ax
	mov ax,3
	push ax
	mov ax,1
	push ax
	reti

| Restore the interrupt vectors in low core.
resvec:	cld
	mov cx,#2*71
	mov si,#_vec_table
	xor di,di
	mov es,di
	rep
	movw
	ret

| Some library routines use exit, so this label is needed.
| Actual calls to exit cannot occur in the kernel.
.globl _exit
_exit:	sti
	jmp _exit

.data
lockvar:	.word 0		| place to store flags for lock()/restore()
vidlock:	.word 0		| dummy variable for use with lock prefix
splimit:	.word 0		| stack limit for current task (kernel only)
tmp:		.word 0		| count of bytes already copied
stkoverrun:	.asciz "Kernel stack overrun, task = "
_vec_table:	.zerow 142	| storage for interrupt vectors
