
	.ALPHA
	.MODEL SMALL
	.DATA?
;
; Memory management system
; Some Assembly Required
; Richard Grehan -- BYTE Magazine
; NOTE: If you need to see how the interface works, locate
; a copy of the MEMTEST.C file.
;
; Local storage.
;
; List pointers
NUPTRLST DW	?	;Head of in use pointer list
FPTRLST	DW	?	;Head of free pointer node list
FREE_START DW	?	;Segment of free memory start (pointer nodes)
HEAP_START DW	?	;Segment of start of heap area
HEAP_LEFT  DW	?	;Space remaining in heap data area
;
; Used during compaction.
; (CUBASE and CULEN define the current "bubble" of unused memory that
; we want percolated to the top of memory.
CUBASE	DW	?	;Current base
CULEN	DW	?	;Current length
SAVEBLK	DW	?	;Saved block number
DRTYFLG	DB	?	;Dirty flag

	.CODE

;
; >>>Turbo C interface routines follow<<<
;
	PUBLIC	_initmem
;_initmem
;This routine initializes the memory management package.
;Call it with the number of pointer blocks you want to allocate.
;The routine returns with the number of available paragraphs of
;memory.
; unsigned int intimem(numptrs)
; unsigned int numptrs;
_initmem:
	PUSH	BP
	MOV	BP,SP
	PUSH	SI
	PUSH	DI
;We need to find out how much memory is available to us.
;Do this by asking for FFFFH paragraphs.  The call will fail, but
;the system will tell us the largest available amount.
	MOV	AH,48H
	MOV	BX,0FFFFH	;Grab more than you could possibly g
	INT	21H
;We will assume failure here.
	PUSH	BX		;Save # of paragraphs available
	MOV	AH,48H
	INT	21H		;Do it again
	POP	BX
	JNC	_ini1		;Better not fail!
	XOR	AX,AX		;Show failure
	POP	DI
	POP	SI
	POP	BP
	RET
_ini1:
	XCHG	AX,BX		;Exchange the pointers
	MOV	CX,[BP+4]	;Get number of nodes he wants
	CALL	INIT_HEAP	;Initialize the heap area
	MOV	AX,HEAP_LEFT	;Return amout available for memory
	POP	DI
	POP	SI
	POP	BP
	RET

	PUBLIC	_gethandle
;_gethandle
; Call this function with the number of paragraphs you want to
; allocate.  It returns the handle to that memory.
; A zero return means the request failed.
;  unsigned int gethandle(numparas)
;  unsigned int numparas
_gethandle:
	PUSH	BP
	MOV	BP,SP
	PUSH	SI
	PUSH	DI
	MOV	AX,FREE_START
	MOV	ES,AX		;Set up ES register
	MOV	DX,[BP+4]	;Get number of paragraphs
	CALL	ALLOC_BLK
	MOV	AX,DI
	POP	DI
	POP	SI
	POP	BP
	RET

	PUBLIC	_freehandle
;_freehandle
; Call this function with the handle to a node pointer you want
; to return to the heap.
; unsigned int freehandle(hnum)
; unsigned int hnum;
_freehandle:
	PUSH	BP
	MOV	BP,SP
	PUSH	SI
	PUSH	DI
	MOV	AX,FREE_START
	MOV	ES,AX		;Set up ES register
	MOV	DI,[BP+4]
	CALL	FREE_BLK
	POP	DI
	POP	SI
	POP	BP
	RET

	PUBLIC	_lockhandle
;_lockhandle
; Call this function with the handle to a node pointer you want to lock.
; unsigned int lockhandle(hnum)
; unsigned int hnum
_lockhandle:
	PUSH	BP
	MOV	BP,SP
	PUSH	DI
	MOV	AX,FREE_START
	MOV	ES,AX
	MOV	DI,[BP+4]
	OR	WORD PTR ES:[DI+2],8000H
	POP	DI
	POP	BP
	RET

	PUBLIC _unlockhandle
;_unlockhandle
; The reverse of lockhandle.
; unsigned int unlockhandle(hnum)
; unsigned int hnum;
_unlockhandle:
	PUSH	BP
	MOV	BP,SP
	PUSH	DI
	MOV	AX,FREE_START
	MOV	ES,AX
	MOV	DI,[BP+4]
	AND	WORD PTR ES:[DI+2],7FFFH
	POP	DI
	POP	BP
	RET

	PUBLIC _handtoseg
;_handtoseg
; Call this routine to get the base segment of a block of memory
; pointed to by a handle.
; handtosegoff(hnum,seg)
; unsigned int hnum;
; unsigned int *seg;
_handtoseg:
	PUSH	BP
	MOV	BP,SP
	PUSH	DI
	MOV	AX,FREE_START
	MOV	ES,AX
	MOV	DI,[BP+4]
	MOV	AX,ES:[DI-4]
	MOV	DI,[BP+6]
	MOV	[DI],AX
	POP	DI
	POP	BP
	RET
;
;INIT_HEAP
;entry: AX=# of paragraphs in free memory
;       BX=starting segment of free memory
;       CX=number of pointer nodes requested
;
INIT_HEAP:
	PUSH	ES		;Save
	PUSH	DI
	MOV	FREE_START,BX
	MOV	ES,BX
;First, calculate number of bytes in the pointer
;area.
	MOV	DX,CX
	SHL	DX,1		;# of pointers * 8
	SHL	DX,1
	SHL	DX,1
;Calculate the starting segment of the heap data
;area.
	ADD	DX,15
	SHR	DX,1
	SHR	DX,1
	SHR	DX,1
	SHR	DX,1
;Calculate remaining paragraphs
	SUB	AX,DX
	MOV	HEAP_LEFT,AX
	ADD	DX,BX
	MOV	HEAP_START,DX
;Set up initial block
	MOV	WORD PTR NUPTRLST,2
	XOR	DI,DI		;First pointer
	XCHG	DX,AX
	STOSW			;Base
	MOV	AX,DX
	STOSW			;Length
	XOR	AX,AX
	STOSW			;No next pointer
	STOSW			;No previous pointer
;Build linked list of free pointers.
	MOV	WORD PTR FPTRLST,6
	DEC	CX		;Don't use first pointer
	ADD	DI,4		;Bump to second pointer
ILP1:	MOV	AX,DI
	ADD	AX,8
	SHR	AX,1		;Steal bit
	STOSW
	ADD	DI,6
	LOOP	ILP1
	SUB	DI,8		;Put terminator on tail
	XOR	AX,AX
	STOSW
;Restore registers and go home
	POP	DI
	POP	ES
	RET
;
;ALLOC_BLK
; On entry: DX holds number of paragraphs you want to allocate.
;           ES holds segment of pointer region.
; On exit:  DI holds byte offset to pointer node referencing allocated
;           block.  If the routine was unable to fulfill the request, DI
;           holds 0.
;Note: this routine uses a first-fit algorithm.
ALLOC_BLK:
	XOR	CH,CH		;Borrow CH as a loop flag
ALL1:
	MOV	DI,NUPTRLST	;Get head of list
	SHL	DI,1		;Make it a byte offset
;See if the memory block is in use
ALL2:
	TEST	WORD PTR ES:[DI],8000H
	JNZ	ALL3
;The block is free...does it reference enough space?
	CMP	DX,ES:[DI-2]
	JA	ALL3
;Great!  I'll take this one!
	JMP	CLAIM_BLK	;Exit through CLAIM-BLK
;The block is either in use, or is too small.
;Try the next block.
ALL3:
	MOV	DI,ES:[DI]
	SHL	DI,1
	OR	DI,DI		;Are we at the end of the list?
	JNZ	ALL2
;We've hit the end of the list...
;Try to compact things
ALL4:
	PUSH	DX
	CALL	COMPACT
	POP	DX
	CMP	CX,DX		;Did we free up enough memory?
	JAE	ALL1		;Yes, go git it.
	XOR	DI,DI		;No, return in shame.
	RET
;
;FREE_BLK
; DI is the BYTE offset to the pointer node of the memory block to make
; free.  ES is assumed to hold the segment of the pointer area.
;
FREE_BLK:
	PUSH	BX
	PUSH	CX
;Get the length of the current block and save it
	MOV	CX,ES:[DI-2]	;Get length of current block
;See if there's a following block.
	MOV	BX,ES:[DI]	;Get next pointer
	SHL	BX,1		;Make it a byte pointer
	OR	BX,BX		;See if it exists
	JZ	DEL1
;There is a following block.  See if it is in use.
	TEST	ES:[BX],8000H
	JNZ	FRE1
;Following block is not in use.  Add its length to current.
;Then delete the following node pointer.
DEL1:
	ADD	CX,ES:[BX-2]
	PUSH	DI
	MOV	DI,BX
	CALL	RELEASE_PTR
	POP	DI
;See if there's a previous block
FRE1:	MOV	BX,ES:[DI+2]	;Get previous pointer
	SHL	BX,1		;Make it a byte pointer
	OR	BX,BX		;And see if it exists
	JZ	FRE2
;Previous block exists, see if it is in use.
	TEST	WORD PTR ES:[BX],8000H
	JS	FRE2
;The previous block is not in use -- add length of current
;block to it.  Then delete current block.
	ADD	WORD PTR ES:[BX-2],CX
	CALL	RELEASE_PTR
	POP	CX
	POP	BX
	RET
;Previous block exists or is in use.  Set current block as
;being freed.
FRE2:	MOV	ES:[DI-2],CX
	AND	WORD PTR ES:[DI],7FFFH
	POP	CX
	POP	BX
	RET
;
;UNLOCK_BLOCK
; On entry: DI holds BYTE offset of pointer node referencing block
;	    to unlock.  ES holds segment of pointer area.
UNLOCK_BLOCK:
	AND	WORD PTR ES:[DI+2],7FFFH
	RET
;
;RELEASE_PTR
; On entry: DI holds BYTE offset to a pointer to return to free list.
;           ES is assumed to hold segment of pointer area
;  Note that this routine does NOTHING with any heap memory that
;  the pointer may be referencing.
RELEASE_PTR:
	PUSH	AX		;Save
	PUSH	BX
	PUSH	CX
;First remove block from list
	MOV	AX,ES:[DI]	;AX=word ptr to next node
	AND	AH,7FH
	MOV	CX,ES:[DI+2]	;CX=word ptr to prev. node
	AND	CH,7FH
	MOV	BX,AX
	ADD	BX,BX		;Next node exist?
	JZ	RLS1
;Next node exists-his previous node is our old previous node
	AND	WORD PTR ES:[BX+2],8000H	;Clear lo 15 bits
	ADD	ES:[BX+2],CX			;Attach chain
;See if previous node exists
RLS1:	MOV	BX,CX
	ADD	BX,BX
	JZ	RLS2
;Previous node exists-his next node is our old next node
	AND	WORD PTR ES:[BX],8000H
	ADD	ES:[BX],AX
;Now put us on the free pointer list
RLS2:
	MOV	AX,FPTRLST	;We now point to head...
	MOV	ES:[DI],AX	;...of old free-pointer list.
	MOV	AX,DI
	SHR	AX,1		;Store word pointer.
	MOV	FPTRLST,AX
	POP	CX
	POP	BX
	POP	AX
	RET
;
;GET_PTR
; Returns in DI byte offset to a pointer block from the free list.
; Returns 0 if list empty.  The pointer block returned is available
; for use.
;
GET_PTR:
	PUSH	AX
	MOV	DI,FPTRLST
	ADD	DI,DI		;Make byte offset
	JZ	GP1
	MOV	AX,ES:[DI]
	MOV	FPTRLST,AX
GP1:	POP	AX
	RET
;
;CLAIM_BLK
; DI holds byte offset to pointer block.
; DX holds # of paragraphs to claim from this block.
; Assumes ES holds segment to pointer region.
; This routine automatically sets the inuse bit on the block.
; Returns DI=0 if fail, else DI is the pointer to the claimed block.
CLAIM_BLK:
	PUSH	AX
	PUSH	BX
	MOV	AX,ES:[DI-2]	;Get length
	OR	WORD PTR ES:[DI],8000H	;Show block as in use.
	SUB	AX,DX		;Grab what you can
	JNZ	CBLK1		;Something left?
;If nothing left, just go home
CBLK0:	POP	BX
	POP	AX
	RET
;If something left, request a new pointer node and tie it in
;with the current pointer node.  First, though, adjust the
;length of the current block.
CBLK1:	MOV	ES:[DI-2],DX	;Adj. len.
	ADD	DX,ES:[DI-4]	;Get new base.
	MOV	BX,DI		;Save pointer in BX
	CALL	GET_PTR		;Get a new pointer node
	OR	DI,DI
	JZ	CBLK0		;Augh!
	MOV	ES:[DI-2],AX	;New length
	MOV	ES:[DI-4],DX	;New base
;New pointer node's next pointer is current 
;pointer node's next pointer
	MOV	DX,ES:[BX]
	AND	DH,7FH		;Clear the in-use bit.
	MOV	ES:[DI],DX
;New pointer node's prev. pointer is current pointer node.
	MOV	AX,BX
	SHR	AX,1
	MOV	ES:[DI+2],AX
;Current pointer node's next pointer is new node.
	MOV	AX,DI
	SHR	AX,1
	AND	WORD PTR ES:[BX],8000H
	ADD	ES:[BX],AX
;Successor node's (if successor exists) previous pointer is new
;node
	SHL	DX,1		;Successor exists?
	OR	DX,DX
	JZ	CBLK2
	MOV	DI,DX
	AND	WORD PTR ES:[DI+2],8000H
	ADD	ES:[DI+2],AX
;All linking done...go home
CBLK2:
	MOV	DI,BX
	POP	BX
	POP	AX
	RET
;
;COMPACT
; Performs a general compaction on the heap.
; Starts at low address, and works to top of heap.
; Locked blocks may cause this routine to fail to
; coalesce all fragments.
; COMPACT returns in CX the largest available free memory block.
; If no pointers are available, COMPACT returns with CX=0.
;
COMPACT:
	XOR	AX,AX
	MOV	CUBASE,AX	;Clear base
	MOV	CULEN,AX	;Clear length
	MOV	SAVEBLK,AX	;Clear saved block number
	MOV	DRTYFLG,AL	;Clear dirty flag
	MOV	CX,AX		;Clear largest free block storage
;Start at lowest block
	MOV	DI,NUPTRLST
	SHL	DI,1
;Look for next block
CMPCT1:
	OR	DI,DI
	JNZ	CMPCT1A		;End of list?
	JMP	CMPCT8
CMPCT1A:
	TEST	WORD PTR ES:[DI],8000H
	JNZ	CMPCT1B
	JMP	CMPCT4
;Block is in use -- see if it is movable
CMPCT1B:
	TEST	WORD PTR ES:[DI+2],8000H
	JNZ	CMPCT3
;Block is not locked -- see if we need to move it.
	MOV	AX,CUBASE	;Anything in base/length?
	OR	AX,CULEN
	JZ	CMPCT2
;Move this block.
	MOV	AX,ES:[DI-4]	;Source
	MOV	BX,CUBASE	;Destination
	MOV	DX,ES:[DI-2]	;Length
	PUSH	DX		;Save stuff
	PUSH	DS
	PUSH	ES
	MOV	DS,AX
	MOV	ES,BX
	CALL	copy_down	;Do the move
	POP	ES
	POP	DS
;Adjust the base address in the block
	MOV	ES:[DI-4],BX	;New base is old CUBASE
	POP	DX
	ADD	CUBASE,DX	;Adjust saved base
;Set the dirty bit.
	MOV	BYTE PTR DRTYFLG,1
;Move to the next pointer node.
CMPCT2:
	MOV	BX,DI		;Save so we know previous pointer
	MOV	DI,ES:[DI]
	SHL	DI,1
	JMP	CMPCT1
;Block is locked.  See if the dirty bit is set.  If so,
;we need to release the pointer node in SAVEBLK, then create
;a new pointer node to hold the contents of CUBASE and CULEN.
CMPCT3:
	MOV	AL,DRTYFLG
	OR	AL,AL
	JZ	CMPCT2		;No dirty bit...skip everything.
	PUSH	DI		;Save current pointer
	MOV	DI,SAVEBLK
	CALL	RELEASE_PTR
	CALL	GET_PTR	
	OR	DI,DI
	JNZ	CMPCT3AA
	JMP	CMPCTZ		;Yikes!! No pointers!
CMPCT3AA:
	MOV	AX,CUBASE
	MOV	ES:[DI-4],AX
	MOV	AX,CULEN
	MOV	ES:[DI-2],AX
	POP	BX		;Retrieve current pointer.
;Tie pointer node in just before current pointer node.
	MOV	AX,BX
	SHR	AX,1
	MOV	ES:[DI],AX	;Set new node's next pointer
	MOV	AX,ES:[BX+2]
	AND	AH,07FH
	MOV	ES:[DI+2],AX	;Set new block's prev pointer
;Fix next node's previous pointer
	MOV	DX,DI
	SHR	DX,1
	AND	WORD PTR ES:[BX+2],8000H
	ADD	ES:[BX+2],DX
;Fix previous node's (if any) next ptr.
	SHR	DI,1
	SHL	AX,1
	OR	AX,AX
	JNZ	CMPCT3A
;This node is first in line.
	MOV	NUPTRLST,DI
	MOV	DI,BX
	JMP	CMPCT3B
;This node is not first in line...go ahead and fix the previous
;node's pointers.
CMPCT3A:
	MOV	BX,AX
	AND	WORD PTR ES:[BX+2],8000H
	ADD	ES:[BX],DI
	POP	DI
CMPCT3B:
	MOV	BYTE PTR DRTYFLG,0
	JMP	CMPCT2		;Go to next block
;Block is not in use.  See if something is in base/len, and
;if so...delete the old copy of SAVEBLK.  This pointer node
;then becomes the new SAVEBLK.
CMPCT4:	
	MOV	AX,CUBASE
	OR	AX,CULEN
	JZ	CMPCT6
	MOV	BX,DI		;Save DI
	MOV	DI,SAVEBLK
	CALL	RELEASE_PTR	;Delete the pointer node
	MOV	DI,BX
	MOV	AX,ES:[DI-2]	;Accumulate length
	ADD	CULEN,AX
	CMP	CX,CULEN	;Is this new length greater than before?
	JAE	CMPCT5
	MOV	CX,CULEN
CMPCT5:
	MOV	AX,ES:[DI-4]	;New base
	MOV	CUBASE,AX
	MOV	SAVEBLK,DI
	JMP	CMPCT3B
CMPCT6:
	MOV	AX,ES:[DI-2]
	MOV	CULEN,AX
	CMP	CX,AX		;New length greater than before?
	JAE	CMPCT7
	MOV	CX,AX
CMPCT7:
	JMP	CMPCT5
;We've hit the end of the list...is CUBASE/CULEN set?
CMPCT8:
	MOV	AX,CUBASE
	OR	AX,CULEN
	JZ	CMPCTX
;BASE/LEN set...see if dirty bit is set.
	CMP	BYTE PTR DRTYFLG,0
	JZ	CMPCTX
;Release saved pointer node.  Create a new pointer node to hold
;CUBASE/CULEN and attach this node to the end of the list.
	MOV	DI,SAVEBLK
	CALL	RELEASE_PTR
	CALL	GET_PTR
	OR	DI,DI
	JZ	CMPCTZ		;No nodes?! We're sunk!
	MOV	AX,CUBASE
	MOV	ES:[DI-4],AX
	MOV	AX,CULEN
	MOV	ES:[DI-2],AX
	MOV	WORD PTR ES:[DI],0	;End of list.
	SHR	BX,1
	MOV	ES:[DI+2],BX
	SHL	BX,1
	SHR	DI,1
	MOV	ES:[BX],DI
;Show safe return
CMPCTX:
	RET
;Error return
CMPCTZ:
	XOR	CX,CX
	RET
;
;
;copy_down
;entry: DS = Source segment base
;       ES = destination segment base
;       DX = # of paragraphs to move
;This routine moves DX paragraphs from source to destination.
;It presumes than DS>ES.
copy_down:
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	SI
	PUSH	DI
	MOV	BX,1000H	;Used a lot
CDLP1:	CMP	DX,BX		;More than 64K left?
	JB	CDLP2
	MOV	CX,32768	;32K words
	XOR	SI,SI		;>>>May not be necessary on...
	MOV	DI,SI		;...later loops
	REP	MOVSW
;Shift everyone by 64K
	MOV	AX,DS
	ADD	AX,BX
	MOV	DS,AX
	MOV	AX,ES
	ADD	AX,BX
	MOV	ES,AX
	SUB	DX,BX
	JMP	CDLP1
CDLP2:	SHR	DX,1		;Calc words remaining
	SHR	DX,1
	SHR	DX,1
	MOV	CX,DX
	XOR	SI,SI
	MOV	DI,SI
	REP	MOVSW
	POP	DI
	POP	SI
	POP	CX
	POP	BX
	POP	AX
	RET
;
;copy_up
;entry: DS = Source paragraph
;       ES = Destination paragraph
;       DX = Number of paragraphs to move
;
copy_up:
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	SI
	PUSH	DI
	MOV	AX,DS
	MOV	BX,ES		;Used a lot
;Set DS/ES to top of window
	ADD	AX,DX
	MOV	DS,AX
	ADD	BX,DX
	MOV	ES,BX
	STD			;Autodecrement
;See if number of bytes to move is <64K
CUP1:	CMP	DX,1000H
	JB	CUP2
;Shift segment registers down 64K
	SUB	AH,10H
	MOV	DS,AX
	SUB	BH,10H
	MOV	ES,BX
;Set up SI/DI, CX, and do the move
	MOV	SI,0FFFEH
	MOV	DI,SI
	MOV	CX,32768
	REP	MOVSW
;Subtract 64K from DX
	SUB	DH,10H
	JMP	CUP1
;Move remainder less than 64K
CUP2:	SUB	AX,DX
	MOV	DS,AX
	SUB	BX,DX
	MOV	ES,BX
	SHL	DX,1
	SHL	DX,1
	SHL	DX,1	;Number of words left to move
	MOV	CX,DX
	SHL	DX,1
	DEC	DX
	DEC	DX	;Byte offset to start
	MOV	SI,DX
	MOV	DI,DX
	REP	MOVSW
	POP	DI
	POP	SI
	POP	CX
	POP	BX
	POP	AX
	RET

;
;make_phys
;Converts segment/offset address to 32-bit physical address.
;entry: AX=segment, BX=offset
;exit:  AX=hi 16-bits of phys addr
;       BX=lo 16-bits of phys addr
make_phys:
	PUSH	CX		;Save registers
	PUSH	DX
	MOV	CX,4		;Bits to shift
	ROL	AX,CL		;Align segment
	MOV	DX,AX		;Save in DX
	AND	DX,0FFF0H	;Clear lo nibble in DX
	AND 	AX,0FH		;Save lo nibble in AX
	ADD	BX,DX		;Lo 16-bits in BX
	ADC	AX,0		;Get carry
	POP	DX		;Restore
	POP	CX
	RET

;make_segoff
;Converts 32-bit physical address in AX/BX to segment/offset.
;Note that the resulting segment/offset is automatically normalized.
;entry: AX=hi 16-bits of phys addr
;       BX=lo 16-bits of phys addr
;exit: AX=segment, BX=offset
make_segoff:
	PUSH 	DX		;Save register
	MOV	DX,BX
	AND	DX,0FH		;Save lo nibble
	SHR	AX,1		;Shift high part back into segment
	RCR	BX,1
	SHR	AX,1
	RCR	BX,1
	SHR	AX,1
	RCR	BX,1
	SHR	AX,1
	RCR	BX,1
	MOV	AX,BX
	MOV	BX,DX
	POP	DX
	RET

;cmp_segoff
;Compare two segment/offset addresses and return comparison based on
;location in physical memory.
;entry: stack contains:
;                  segment   <========2nd entry
;                  offset    <======|
;                  segment   <========1st entry
;                  offset    <======|
;                  return    (1 word)
;exit: entries undisturbed on stack
;      AX is 0 if equal
;            1 if 1>2
;           -1 if 1<2
cmp_segoff:
	PUSH	BP		;Save
	MOV	BP,SP
	PUSH	BX
	PUSH	DX
	PUSH	CX
	MOV	BX,[BP+4]	;1st entry offset
	MOV	AX,[BP+6]	;1st entry segment
	CALL	norm_segoff
	MOV	CX,AX		;Save result
	MOV	DX,BX
	MOV	BX,[BP+8]	;2nd entry offset
	MOV	AX,[BP+10]	;2nd entry segment
	CALL	norm_segoff
	CMP	CX,AX		;Compare segments
	POP	CX		;Done w/CX- restore
	JA	ONEG
	JB	TWOG
	CMP	DX,BX		;Compare offsets
	JA	ONEG
	JB	TWOG
	XOR	AX,AX
	POP	DX
	POP	BX
	RET
ONEG:	MOV	AX,1
	POP	DX
	POP	BX
	RET
TWOG:	MOV	AX,-1
	POP	DX
	POP	BX
	RET

;norm_segoff
;Normalize a segment offset.
;entry: AX=segment, BX=offset
;exit: AX=segment*, BX=offset*
;Physical address pointer to by segment*/offset* is equal to address
;pointed to by original segment/offset.  Only, 0<=offset*<=15.
norm_segoff:
	PUSH	DX		;Save DX
	MOV	DX,BX
	AND	BX,0FH		;Clear low nibble
	SHR	DX,1		;Retrieve segment portion of old offset
	SHR	DX,1
	SHR	DX,1
	SHR	DX,1
	ADD	AX,DX
	POP	DX
	RET

	END