Minix1.5/kernel/klib88.x

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

.define	_bios13		| make BIOS 13 call for disk I/O (real mode only)
.define	_build_sig	| build 4 word structure pushed onto stack for signals
.define	_check_mem	| check a block of memory, return the valid size
.define	_cim_at_wini	| clear the AT winchester interrupt mask
.define	_cim_floppy	| clear the floppy interrupt mask
.define	_cim_printer	| clear the printer interrupt mask
.define	_cim_xt_wini	| clear the XT winchester interrupt mask
.define	_cp_mess	| copies messages from source to destination
.define	_exit		| dummy for library routines
.define	.fat		| dummy for library routines
.define _get_byte	| read a byte from anywhere and return it
.define	_in_byte	| read a byte from a port and return it
.define	_klib_1hook	| init from real mode for real or protected mode
.define	_klib_2hook	| init from protected mode for real or protected mode
.define	_lock		| disable interrupts
.define	_out_byte	| write a byte to a port
.define	_phys_copy	| copy data from anywhere to anywhere in memory
.define	_port_read	| transfer data from (disk controller) port to memory
.define	_port_write	| transfer data from memory to (disk controller) port
.define	_reset		| reset the system (real mode only)
.define	_scr_down	| scroll screen a line down (in software, by copying)
.define	_scr_up		| scroll screen a line up (in software, by copying)
.define	_sim_printer	| set the printer interrupt mask
.define	_tasim_printer	| test and set the printer interrupt mask
.define	_test_and_set	| test and set locking primitive on a word in memory
.define	.trp		| dummy for library routines
.define	_unlock		| enable interrupts
.define	_vid_copy	| copy data to video ram (perhaps during retrace only)
.define	_wait_retrace	| wait for retrace interval

| 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.

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

#if INTEL_32BITS
#error /* this is not the 32-bit version */
#endif

#define DS_286_OFFSET	DS_286_INDEX*DESC_SIZE
#define ES_286_OFFSET	ES_286_INDEX*DESC_SIZE
#define EM_MASK		0xFFF0	/* extended memory mask for hi word */
#define EM_XFER_VEC	0x15	/* copy (normal or extended) memory */
#	define EM_XFER_FUNC	0x87
#define JMP_OPCODE	0xE9	/* opcode used for patching */
#define OFF_MASK	0x000F	/* offset mask for phys_b -> hclick:offset */

| imported functions

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

| exported variables

	.bss
.define		splimit

| imported variables

.extern		_Ax, _Bx, _Cx, _Dx, _Es
.extern		_blank_color
.extern		_gdt
.extern		_protected_mode
.extern		_snow
.extern		_vec_table
.extern		_vid_mask
.extern		_vid_port

	.text
|*===========================================================================*
|*				bios13					     *
|*===========================================================================*
| PUBLIC void bios13();

_bios13:			| make a BIOS 0x13 call for disk I/O
	push ax			| save the registers
	push bx
	push cx
	push dx
	push es
	mov ax,_Ax		| load parameters
	mov bx,_Bx
	mov cx,_Cx
	mov dx,_Dx
	mov es,_Es
	int 0x13		| make the BIOS call
	mov _Ax,ax		| save results
	mov _Bx,bx
	mov _Cx,cx
	mov _Dx,dx
	pop es
	pop dx
	pop cx
	pop bx
	pop ax
	ret


|*===========================================================================*
|*				build_sig				     *
|*===========================================================================*
| PUBLIC void build_sig(char *sig_stuff, struct proc *rp, int 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.

_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,PCREG(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,PSWREG(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


|*===========================================================================*
|*				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.
| The memory and initial size must be <= 1M for non-protected mode.
| An initial size of 0 means everything.
| This really should do some alias checks.

_check_mem:
	push	bp
	mov	bp,sp
	push	ds
	mov	dx,4+2(bp)	| base in dx:ax
	sub	ax,ax		| prepare for early exit
	test	dx,#notop(HCHIGH_MASK)
	jnz	cm_1exit	| can't handle bases above 1M
	mov	ax,4(bp)	| ax = base segment = base / 16 % 0x10000
	andb	al,#HCLOW_MASK
	orb	al,dl
	movb	cl,#HCLICK_SHIFT
	ror	ax,cl
	mov	bx,4+4(bp)	| size in dx:bx
	mov	dx,4+4+2(bp)
	test	dx,#notop(HCHIGH_MASK)
	jz	over_cm_reduce
	movb	dl,#HCHIGH_MASK
	mov	bx,#0xFFFF
over_cm_reduce:
	andb	bl,#HCLOW_MASK	| cx = size in hclicks = size / 16 % 0x10000
	orb	bl,dl
	ror	bx,cl
	mov	cx,bx
	push	cx		| save size in clicks
	mov	bx,4(bp)	| bx = base offset = base % 16
	and	bx,#OFF_MASK
cm_loop:
	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
	inc	ax		| next segment, test for wraparound at 1M
	loopnz	cm_loop
cm_exit:
	pop	ax
	sub	ax,cx		| verified size in phys_clicks
cm_1exit:
	movb	dl,ah		| convert to phys_bytes in dx:ax
	movb	cl,#HCLICK_SHIFT
	shl	ax,cl
	shr	dx,cl
	and	dx,#HCHIGH_MASK
	pop	ds
	pop	bp
	ret


|*===========================================================================*
|*				cim_at_wini				     *
|*				cim_floppy				     *
|*				cim_printer				     *
|*				cim_xt_wini				     *
|*===========================================================================*
| All these routines are meant to be called from the task level where
| interrupts should not be globally disabled, so they return with interrupts
| enabled.

| PUBLIC void cim_at_wini();
| Clear the AT winchester interrupt mask.

_cim_at_wini:
	cli
	in	INT2_MASK
	andb	al,#notop(AT_WINI_MASK)
	out	INT2_MASK
	sti
	ret

| PUBLIC void cim_floppy();
| Clear the AT winchester interrupt mask.

_cim_floppy:
	cli
	in	INT_CTLMASK
	andb	al,#notop(FLOPPY_MASK)
	out	INT_CTLMASK
	sti
	ret

| PUBLIC void cim_printer();
| Clear the printer interrupt mask.

_cim_printer:
	cli
	in	INT_CTLMASK
#if ASLD
	andb	al,#notop(PRINTER_MASK)
#else
	andb	al,#notop(PRINTER_MASK) & 0xFF
#endif
	out	INT_CTLMASK
	sti
	ret

| PUBLIC void cim_xt_wini();
| Clear the xt_wini interrupt mask.

_cim_xt_wini:
	cli
	in	INT_CTLMASK
	andb	al,#notop(XT_WINI_MASK)
	out	INT_CTLMASK
	sti
	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:
	push es			| save es
	push ds			| save ds
	mov bx,sp		| index off bx because machine can't 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're finished with the bx pointer.
| We're 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

	stow			| copy sender's process number to dest message
	add si,*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 ds			| restore ds
	pop es			| restore es	
	ret			| that's 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
| Same for .trp.

_exit:
.fat:
.trp:
	sti
	j _exit

|*===========================================================================*
|*				get_byte				     *
|*===========================================================================*
| PUBLIC u16_t get_byte(u16_t segment, 16_t *offset);
| Load and return the byte at the far pointer  segment:offset.

_get_byte:
	mov	cx,ds		| save ds
	pop	dx		| return adr
	pop	ds		| segment
	pop	bx		| offset
	sub	sp,#2+2		| adjust for parameters popped
	movb	al,(bx)		| load the byte to return
	xorb	ah,ah		| 
	mov	ds,cx		| restore ds
	jmp	(dx)		| return


|*===========================================================================*
|*				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
	in			| input 1 byte
	subb	ah,ah		| unsign extend
	jmp	(bx)


|*===========================================================================*
|*				klib_1hook				     *
|*===========================================================================*
| PUBLIC void klib_1hook();
| Initialize klib from real mode for real or protected mode,
| Do nothing for real mode.
| For protected mode, patch some real mode functions at their starts to jump
| to their protected mode equivalents, according to the patch table.

_klib_1hook:
	cmpb	_protected_mode,#0
	jz	hook1_done
	mov	si,#patch_table
	lodw			| original function
patch1:
	mov	bx,ax
	seg	cs
	movb	(bx),#JMP_OPCODE
	lodw			| new function - target of jump
	sub	ax,bx		| relative jump
	sub	ax,#3		| adjust by length of jump instruction
	seg	cs
	mov	1(bx),ax
	lodw			| next original function
	test	ax,ax
	jnz	patch1
hook1_done:
	ret


|*===========================================================================*
|*				klib_2hook				     *
|*===========================================================================*
| PUBLIC void klib_prot_mode_init();
| Initialize klib protected real mode for real or protected mode,
| Do nothing for real mode.
| For protected mode, load idt, task reg and flags.

_klib_2hook:
	cmpb	_protected_mode,#0
	jz	hook2_done
	deflidt	(_gdt+BIOS_IDT_SELECTOR) | loaded by BIOS, but in wrong mode!
	mov	ax,#TSS_SELECTOR	| no other TSS is used
	defltrax
	sub	ax,ax		| zero
	push	ax		| set flags to known good state
	popf			| especially, clear nested task and int enable
hook2_done:
	ret


|*===========================================================================*
|*				lock					     *
|*===========================================================================*
| PUBLIC void lock();
| Disable CPU interrupts.
_lock:
	cli			| disable interrupts
	ret			| return to caller


|*===========================================================================*
|*				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

| check for extended memory

	mov	ax,SRCHI(bp)
	or	ax,DESTHI(bp)
	test	ax,#EM_MASK
	jnz	to_em_xfer

	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
				| mask is unnecessary because of EM_MASK test
	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 (can't 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		| can't underflow, so carry clear now for rcr
	rcr	cx,#1		| count in words, carry remembers if byte
	jnc	pc_even		| no odd byte
	movb			| copy odd byte
pc_even:
	rep			| copy 1 word at a time
	movw			| 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 doesn't
	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
	j	pc_loop		| start next iteration

| When source or target is above 1M, join em_xfer.
| Rely on MM and MEMTASK not to provide bad parameters, and omit checking the
| following:
|	count must be even
|	count must be < 64K
|	machine must be AT-compatible
| which are not required for phys_copy.

to_em_xfer:
	shr	COUNTLO(bp),#1	| convert count to words
	pop	bp		| stack frame now agrees with em_xfer's
	j	_em_xfer


|*===========================================================================*
|*				em_xfer					     *
|*===========================================================================*
|  This file contains one routine which transfers words between user memory
|  and extended memory on an AT or clone.  A BIOS call (INT 15h, Func 87h)
|  is used to accomplish the transfer.  The BIOS call is "faked" by pushing
|  the processor flags on the stack and then doing a far call through the
|  saved vector table to the actual BIOS location.  An actual INT 15h would
|  get a MINIX complaint from an unexpected trap.

|  This particular BIOS routine runs with interrupts off since the 80286
|  must be placed in protected mode to access the memory above 1 Mbyte.
|  So there should be no problems using the BIOS call, except it may take
|  too long and cause interrupts to be lost.
|
	.text
gdt:				| Begin global descriptor table
					| Dummy descriptor
	.word 0		| segment length (limit)
	.word 0		| bits 15-0 of physical address
	.byte 0		| bits 23-16 of physical address
	.byte 0		| access rights byte
	.word 0		| reserved
					| descriptor for GDT itself
	.word 0		| segment length (limit)
	.word 0		| bits 15-0 of physical address
	.byte 0		| bits 23-16 of physical address
	.byte 0		| access rights byte
	.word 0		| reserved
src:					| source descriptor
srcsz:	.word 0		| segment length (limit)
srcl:	.word 0		| bits 15-0 of physical address
srch:	.byte 0		| bits 23-16 of physical address
	.byte 0x93	| access rights byte
	.word 0		| reserved
tgt:					| target descriptor
tgtsz:	.word 0		| segment length (limit)
tgtl:	.word 0		| bits 15-0 of physical address
tgth:	.byte 0		| bits 23-16 of physical address
	.byte 0x93	| access rights byte
	.word 0		| reserved
					| BIOS CS descriptor
	.word 0		| segment length (limit)
	.word 0		| bits 15-0 of physical address
	.byte 0		| bits 23-16 of physical address
	.byte 0		| access rights byte
	.word 0		| reserved
					| stack segment descriptor
	.word 0		| segment length (limit)
	.word 0		| bits 15-0 of physical address
	.byte 0		| bits 23-16 of physical address
	.byte 0		| access rights byte
	.word 0		| reserved

|
|
|  Execute a transfer between user memory and extended memory.
|
|  status = em_xfer(source, dest, count);
|
|    Where:
|       status => return code (0 => OK)
|       source => Physical source address (32-bit)
|       dest   => Physical destination address (32-bit)
|       count  => Number of words to transfer
|
|
|
_em_xfer:
	push	bp		| Save registers
	mov	bp,sp
	push	si
	push	es
	push	cx
|
|  Pick up source and destination addresses and update descriptor tables
|
	mov ax,4(bp)
	seg cs
	mov srcl,ax
	mov ax,6(bp)
	seg cs
	movb srch,al
	mov ax,8(bp)
	seg cs
	mov tgtl,ax
	mov ax,10(bp)
	seg cs
	movb tgth,al
|
|  Update descriptor table segment limits
|
	mov cx,12(bp)
	mov ax,cx
	add ax,ax
	seg cs
	mov tgtsz,ax
	seg cs
	mov srcsz,ax

|
|  Now do actual DOS call
|
	push cs
	pop es
	mov si,#gdt
	movb ah,#EM_XFER_FUNC
	pushf			| fake interrupt
	calli	@_vec_table+4*EM_XFER_VEC

|
|  All done, return to caller.
|

	pop	cx		| restore registers
	pop	es
	pop	si
	mov	sp,bp
	pop	bp
	ret


|*===========================================================================*
|*				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
	out			| output 1 byte
	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	cx
	push	dx
	push	di
	push	es
	mov	ax,4+2(bp)	| destination addr in dx:ax
	mov	dx,4+2+2(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
	movb	cl,#HCLICK_SHIFT
	ror	ax,cl
	mov	es,ax

	mov	cx,4+2+4(bp)	| count in bytes
	shr	cx,#1		| count in words
	mov	dx,4(bp)	| port to read from
	rep
	insw
	pop	es
	pop	di
	pop	dx
	pop	cx
	mov	sp,bp
	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	cx
	push	dx
	push	si
	push	ds
	mov	ax,4+2(bp)	| source addr in dx:ax
	mov	dx,4+2+2(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
	movb	cl,#HCLICK_SHIFT
	ror	ax,cl
	mov	ds,ax
	mov	cx,4+2+4(bp)	| count in bytes
	shr	cx,#1		| count in words
	mov	dx,4(bp)	| port to read from
	rep
	outsw
	pop	ds
	pop	si
	pop	dx
	pop	cx
	mov	sp,bp
	pop	bp
	ret


|*===========================================================================*
|*				reset					     *
|*===========================================================================*
| PUBLIC void reset();
| Reset the system.
| This only works in real mode.
| For protected mode, it would be necessary to trap to privilege 0, then do
| something fatal like loading an IDT with offset 0 and interrupting.

_reset:
	jmpi	0,0xFFFF


|*===========================================================================*
|*				scr_down & scr_up			     *
|*===========================================================================*
| PUBLIC void scr_down(unsigned videoseg, int source, int dest, int count);
| Scroll the screen down one line.
|
| PUBLIC void scr_up(unsigned videoseg, int source, int dest, int count);
| Scroll the screen up one line.
|
| These are identical except scr_down() must reverse the direction flag
| during the copy to avoid problems with overlap.

_scr_down:
	std
_scr_up:
	push	bp
	mov	bp,sp
	push	si
	push	di
	push	ds
	push	es
	mov	ax,4(bp)	| videoseg (selector for video ram)
	mov	si,6(bp)	| source (offset within video ram)
	mov	di,8(bp)	| dest (offset within video ram)
	mov	cx,10(bp)	| count (in words)
	mov	ds,ax		| set source and dest segs to videoseg
	mov	es,ax
	rep			| do the copy
	movw
	pop	es
	pop	ds
	pop	di
	pop	si
	pop	bp
	cld			| restore (unnecessarily for scr_up)
	ret


|*===========================================================================*
|*				sim_printer				     *
|*===========================================================================*
| PUBLIC void sim_printer();
| Set the printer interrupt mask.
| This is meant to be called from the task level, so it returns with
| interrupts enabled.

_sim_printer:
	cli
	in	INT_CTLMASK
	orb	al,#PRINTER_MASK
	out	INT_CTLMASK
	sti
	ret


|*===========================================================================*
|*				tasim_printer				     *
|*===========================================================================*
| PUBLIC unsigned tasim_printer();
| Set the printer interrupt mask, indivisibly with getting its old value.
| Return old value.
| This is meant to be called from the task level, so it returns with
| interrupts enabled.
| This might not work for multiple processors, unlike test_and_set().

_tasim_printer:
	cli
	in	INT_CTLMASK
	movb	ah,al
	orb	al,#PRINTER_MASK
	out	INT_CTLMASK
	sti
	movb	al,ah
	and	ax,#PRINTER_MASK
	ret


|*===========================================================================*
|*				test_and_set				     *
|*===========================================================================*
| PUBLIC int test_and_set(int *flag);
| Set the flag to TRUE, indivisibly with getting its old value.
| Return old flag.

_test_and_set:
	pop	dx
	pop	bx
	sub	sp,#2
	mov	ax,#1
	xchg	ax,(bx)
	jmp	(dx)


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

_unlock:
	sti			| enable interrupts
	ret			| return to caller


|*===========================================================================*
|*				vid_copy				     *
|*===========================================================================*
| PUBLIC void vid_copy(char *buffer, unsigned videobase, int offset,
|		       int 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_color) is used
|
| This routine takes a string of (character, attribute) pairs and writes them
| onto the screen.  For a snowy display, the writing only takes places during
| the vertical retrace interval, to avoid displaying garbage on the 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 es,6(bp)		| load es NOW: int routines may NOT ruin it
	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
				| it DOES for jle!!
	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

vid.1:	push cx			| save actual count used for later
	cmpb _snow,#0		| skip vertical retrace test if no snow
	jz vid.4

|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:	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:	pop si			| si = count of words copied
	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
	add si,si		| si = count of bytes copied
	add 4(bp),si		| increment buffer pointer
	j 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_color	| ax = blanking character
	rep			| copy loop
	stow			| blank screen
	j vid.5			| done


|*===========================================================================*
|*			      wait_retrace				     *
|*===========================================================================*
| PUBLIC void wait_retrace();
| Wait for the *start* of retrace period.
| The VERTICAL_RETRACE_MASK of the color vid_port is set during retrace.
| First wait until it is off (no retrace).
| Then wait until it comes on (start of retrace).
| We can't afford to worry about interrupts.

_wait_retrace:
	mov	dx,_vid_port
	orb	dl,#COLOR_STATUS_PORT & 0xFF
wait_no_retrace:
	in
	testb	al,#VERTICAL_RETRACE_MASK
	jnz	wait_no_retrace
wait_retrace:
	in
	testb	al,#VERTICAL_RETRACE_MASK
	jz	wait_retrace
	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_check_mem				     *
|*===========================================================================*
PCM_DENSITY	=	256	| resolution of check
				| the shift logic depends on this being 256
p_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	pcm_1exit	| can't 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
pcm_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	pcm_exit	| if different, memory is unusable
	movb	dl,#TEST2PATTERN
	xchgb	dl,(bx)
	xchgb	dl,(bx)
	cmpb	dl,#TEST2PATTERN
	jnz	pcm_exit
	seg	es		| next segement, test for wraparound at 16M
	add	_gdt+DS_286_OFFSET+DESC_BASE,#PCM_DENSITY
	seg	es		| assuming es == old ds
	adcb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,#0
	loopnz	pcm_loop

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


|*===========================================================================*
|*				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:
	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

	seg	es
	mov	0,bx		| sender's proc no. 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
	movw
	mov	di,bx
	mov	si,ax
	pop	es
	pop	ds
	jmp	(dx)


|*===========================================================================*
|*				p_phys_copy				     *
|*===========================================================================*
p_phys_copy:
	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
	j	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
	movw
	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
	movw			| 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_port_read				     *
|*===========================================================================*
p_port_read:
	pop	bx
	pop	dx		| port
	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
	sub	sp,#2+4+2
	push	es
	mov	ax,#ES_286_SELECTOR
	mov	es,ax
	mov	ax,di
	sub	di,di		| dst offset is now 0 relative to start of seg
	shr	cx,#1		| word count
	rep
	insw			| read everything
	mov	di,ax
	pop	es
	jmp	(bx)


|*===========================================================================*
|*				p_port_write				     *
|*===========================================================================*
p_port_write:
	pop	bx
	pop	dx		| port
	pop	_gdt+DS_286_OFFSET+DESC_BASE
	pop	ax		| pop offset into base of source descriptor
	movb	_gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,al
	pop	cx		| byte count, discard high word
	sub	sp,#2+4+2
	push	ds
	mov	ax,#DS_286_SELECTOR
	mov	ds,ax
	mov	ax,si
	sub	si,si		| src offset is now 0 relative to start of seg
	shr	cx,#1		| word count
	rep
	outsw			| write everything
	mov	si,ax
	pop	ds
	jmp	(bx)


|*===========================================================================*
|*				data					     *
|*===========================================================================*
	.data
patch_table:			| pairs (old function, new function)
	.word	_check_mem, p_check_mem
	.word	_cp_mess, p_cp_mess
	.word	_phys_copy, p_phys_copy
	.word	_port_read, p_port_read
	.word	_port_write, p_port_write
	.word	_restart, p_restart	| in mpx file
	.word	save, p_save	| in mpx file
	.word	0		| end of table
splimit:			| stack limit for current task (kernel only)
	.word 0	
vidlock:			| dummy variable for use with lock prefix
	.word 0