;; umm.asm v0.3
;; Upper Memory Manager for MS-DOS
;; Copyright (C) 1991  Kenneth Gober
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;;
;; To contact the author about changes, enhancements, bug reports, or
;; other comments, send electronic mail to:
;;
;;	snow@drycas (from Bitnet sites)
;;	snow@drycas.club.cc.cmu.edu (from Internet sites)
;;
;; If you are unable to contact the author through electronic mail,
;; try sending a letter (as a last resort, only) to the following address:
;;
;;	Kenneth Gober
;;	412 Robin Road
;;	Cedar Hill, TX 75104 (USA)
;;
;; Please note that mail sent to this address may not yield a response
;; for several months!
;;
;; Version History:
;;
;;	0.0	Initial release for 80386 only.
;;	0.1	Provided 80286 support.
;;	0.2	Memory test added.  Bug in UMB merge code fixed by
;;		  ajr@cybill.inesc.pt (Antonio Julio Raposo).
;;	0.3	Resident portion relocated to upper memory.
;;

	ideal				; Use TASM Ideal mode syntax
	p286n				; Assemble for the 80286 (real mode)
	locals	$$			; local labels preceded by '$$'

mbds	equ	(mb ds:0)
mbes	equ	(mb es:0)
rhds	equ	(rh ds:si)
rhes	equ	(rh es:di)

struc	rh				; request header
len	db	?
dev	db	?
cmd	db	?
st	dw	?
rsvd	dq	?
ct	db	?
aoff	dw	?
aseg	dw	?
dptr	dd	?
ends

struc	mb				; UMB header
nxt	dw	?			; next UMB in chain
siz	dw	?			; size of UMB (without header)
mlo	dw	?			; magic number (low word)
mhi	dw	?			; magic number (high word)
extra	dq	?			; extra (unused)
ends

group	umm	umml, ummh, umm0

segment	umml	use16			; low-memory resident segment

devhdr	dd	-1			; device header
devflg	dw	0a000h
devstr	dw	ummstr
devint	dw	ummint0
devnam	db	'UMMXXXX0'

label	rhptr	dword			; pointer to request header
rhoff	dw	?
rhseg	dw	?

proc	ummstr	far			; strategy routine
	assume	cs:umml

	mov	[umml:rhoff], bx
	mov	[umml:rhseg], es
	ret
endp

proc	ummint	far			; resident interrupt routine
	assume	cs:umml

	push	si			; save registers
	push	ds
	lds	si, [umml:rhptr]	; ds:si = request header
	mov	[rhds.st], 8103h	; return error (unknown command)
	pop	ds			; restore registers
	pop	si
	ret
endp

label	endl	unknown			; HEADER SECTION ENDS HERE

ends	umml

segment	ummh	use16			; upper-memory resident segment

label	begh	unknown

freep	dw	-1			; segment of first free UMB

label	xmmptr	dword			; pointer to XMM
xmmoff	dw	?
xmmseg	dw	?

rpopf:	iret				; for reliable popf

proc	ummctl	far			; UMM control function
	assume	cs:ummh

	jmp	short $$1		; XMS requires this
	nop
	nop
	nop

$$1:	pushf				; save flags
	push	cs
	cmp	ah, 10h			; request UMB
	je	short $$2
	cmp	ah, 11h			; release UMB
	je	short $$3
	call	ummh:rpopf		; restore flags
	jmp	[ummh:xmmptr]		; chain to old XMM

$$2:	push	ds			; save segment registers
	push	es
	call	ummh:requmb		; request UMB
	jmp	short $$4

$$3:	push	ds			; save segment registers
	push	es
	call	ummh:relumb		; release UMB

$$4:	pop	es			; restore segment registers
	pop	ds
	call	ummh:rpopf		; restore flags before returning
	ret
endp

proc	requmb	near			; request UMB
	assume	cs:ummh

	cmp	[ummh:freep], -1	; any UMBs available?
	jne	short $$1
	xor	ax, ax			; return failure code
	mov	bl, 0b1h		; no UMBs available
	ret

$$1:	push	cs			; get paragraph address of freep
	pop	ds
	push	cx
	xor	cx, cx

$$2:	cmp	[mbds.nxt], -1		; last UMB?
	jne	short $$3
	xor	ax, ax			; return failure code
	mov	bl, 0b0h		; smaller UMB available
	mov	dx, cx
	pop	cx
	ret

$$3:	mov	ax, ds			; save previous UMB
	mov	ds, [mbds.nxt]		; move to next UMB
	cmp	[mbds.siz], cx		; remember size of largest UMB
	jbe	short $$4
	mov	cx, [mbds.siz]

$$4:	cmp	cx, dx			; big enough?
	jb	short $$2
	sub	cx, dx			; split into two UMBs if necessary
	cmp	cx, 1
	ja	short $$5
	mov	es, ax			; unlink current UMB
	mov	ax, [mbds.nxt]
	mov	[mbes.nxt], ax
	jmp	short $$6

$$5:	mov	ax, ds			; determine address of new UMB
	add	ax, cx
	dec	cx			; shorten old UMB
	mov	[mbds.siz], cx
	mov	ds, ax			; initialize new UMB
	mov	[mbds.siz], dx
	mov	[mbds.mlo], 4c4bh
	mov	[mbds.mhi], 0047h

$$6:	mov	ax, 1			; return success code
	mov	bx, ds			; return address of UMB
	inc	bx
	pop	cx
	ret
endp

proc	relumb	near			; release UMB
	assume	cs:ummh

	push	dx
	dec	dx
	mov	es, dx
	cmp	[mbes.mlo], 4c4bh	; check magic number
	jne	short $$1
	cmp	[mbes.mhi], 0047h
	je	short $$2

$$1:	xor	ax, ax			; return failure code
	mov	bl, 0b2h		; invalid UMB
	pop	dx
	ret

$$2:	push	cs			; get paragraph address of freep
	pop	ds

$$3:	cmp	[mbds.nxt], dx		; step through linked list
	ja	short $$4
	mov	ds, [mbds.nxt]
	jmp	short $$3

$$4:	mov	ax, [mbds.nxt]		; link UMB back into list
	mov	[mbes.nxt], ax
	mov	[mbds.nxt], dx
	push	cs
	pop	ds			; try to merge adjacent UMBs
	mov	ds, [mbds.nxt]

$$5:	cmp	[mbds.nxt], -1		; end of chain?
	jne	short $$6
	mov	ax, 1			; return success code
	pop	dx
	ret

$$6:	mov	ax, ds			; advance to next UMB
	mov	ds, [mbds.nxt]
	mov	es, ax			; see if UMBs are adjacent
	add	ax, [mbes.siz]
	inc	ax
	mov	dx, ds
	cmp	ax, dx
	jne	short $$5
	mov	ax, [mbds.nxt]		; unlink second UMB
	mov	[mbes.nxt], ax
	mov	ax, [mbds.siz]		; merge UMBs
	inc	ax
	add	[mbes.siz], ax
	push	es			; try merging this UMB again
	pop	ds
	jmp	short $$5
endp

label	endh	unknown			; RESIDENT SECTION ENDS HERE

ends	ummh

segment	umm0	use16			; initialization segment

oldsp	dw	?			; original stack
oldss	dw	?

proc	ummint0	far			; initial interrupt routine
	assume	cs:umm, ds:umm

	pushf				; save flags
	push	cs
	pusha				; save registers
	push	ds
	push	es
	push	cs
	pop	ds
	les	di, [rhptr]		; es:di = request header
	mov	[rhes.st], 8103h	; assume error (unknown command)
	mov	al, [rhes.cmd]		; only cmd 0, INIT is legal
	or	al, al
	jnz	short $$1
	call	init

$$1:	pop	es			; restore registers
	pop	ds
	popa
	call	rpopf			; restore flags before returning
	ret
endp

proc	init	near			; initialize driver
	assume	cs:umm, ds:umm

	mov	[oldss], ss		; save old stack
	mov	[oldsp], sp
	mov	bx, ds
	mov	ss, bx			; enable new stack
	mov	sp, offset stktop
	mov	ah, 9			; write banner
	mov	dx, offset eHello
	int	21h
	mov	[devint], offset ummint	; enable resident interrupt routine
	mov	ax, 4300h		; check for an XMS driver
	int	2fh
	cmp	al, 80h			; is the XMS driver loaded?
	je	short lexer
	mov	dx, offset eNoXMS

abort:	call	error			; abort installation
	mov	[rhes.aseg], cs		; discard everything after header
	mov	[rhes.aoff], offset endl
	mov	[rhes.st], 810ch	; return error (general failure)
	mov	ss, [oldss]		; restore old stack
	mov	sp, [oldsp]
	ret

badch:	pop	ds			; bad character found by lexer
	mov	dx, offset eLexer
	jmp	short abort

lexer:	push	ds			; lexical analyzer
	lds	si, [rhes.dptr]		; ds:si = command line arguments
	cld

s0:	lodsb				; state 0, skip filename
	call	eol
	je	short done
	call	blank
	jne	short s0

s1:	lodsb				; state 1, skip blanks
	call	eol
	je	short done
	call	blank
	je	short s1
	call	digit
	ja	short badch

	cbw
	mov	cx, ax
s2:	lodsb				; state 2, found 1 decimal digit
	cmp	al, '@'
	je	short s4
	call	digit
	ja	short badch

	imul	cx, 10
	add	cx, ax
s3:	lodsb				; state 3, found 2 decimal digits
	cmp	al, '@'
	jne	short badch

s4:	lodsb				; state 4, get first hex digit (a-f)
	call	hexaf
	ja	short badch
	add	al, 10
	mov	bh, al

s5:	lodsb				; state 5, get second hex digit (0-f)
	mov	bl, al
	call	digit
	jbe	short $$1
	mov	al, bl
	call	hexaf
	ja	short badch
	add	al, 10

$$1:	shl	bh, 4
	or	bh, al
	xor	bl, bl

s6:	lodsb				; state 6, get third hex digit (0)
	cmp	al, '0'
	jne	short badch

s7:	lodsb				; state 7, get fourth hex digit (0)
	cmp	al, '0'
	jne	short badch
	call	newumb			; create new UMB
	jmp	short s1

done:	pop	ds			; command-line processing done
	mov	[rhes.aseg], cs		; discard initialization section
	mov	[rhes.aoff], offset endh
	mov	[rhes.st], 0100h	; return success code
	mov	cx, offset ummh:endh	; relocate resident portion
	mov	dx, cx			; find size in paragraphs
	add	dx, 15
	shr	dx, 4
	mov	bx, cs			; call UMM control function
	mov	ax, offset begh		; find segment
	shr	ax, 4
	add	bx, ax
	mov	ah, 10h
	push	bx			; save ummh segment value
	push	cs			; push return address
	push	offset $$ret
	push	bx			; push call address
	push	offset ummh:ummctl
	retf				; this is a far call
$$ret:	test	ax, ax			; UMB available?
	jz	hook
	pop	ax			; replace saved ummh segment value
	push	bx			; and relocate ummh to UMB
	mov	[rhes.aoff], offset endl
	mov	es, bx
	mov	si, offset begh
	xor	di, di
	cld
	rep	movsb

hook:	mov	ax, 4310h		; hook XMS control function
	int	2fh
$$2:	cmp	[byte es:bx], 0ebh	; does it start with a short jump?
	je	short $$3
	les	bx, [es:bx+1]		; if not, follow far jump
	jmp	short $$2
$$3:	mov	[byte es:bx], 0eah	; change to a long jump
	inc	bx			; load byte displacement
	mov	al, [byte es:bx]
	cbw				; convert to word offset
	add	ax, bx
	inc	ax
	pop	ds			; restore saved ummh segment value
	assume	cs:umm, ds:ummh
	mov	[ummh:xmmoff], ax	; link ourselves into the chain
	mov	[ummh:xmmseg], es
	mov	[word es:bx], offset ummh:ummctl
	mov	[word es:bx+2], ds
	mov	ss, [oldss]		; restore old stack
	mov	sp, [oldsp]
	ret
endp

proc	eol	near			; check if al is an eol or eof
	assume	cs:umm

	cmp	al, 13
	je	short $$1
	cmp	al, 26
$$1:	ret
endp

proc	blank	near			; check if al is a space or tab
	assume	cs:umm

	cmp	al, 32
	je	short $$1
	cmp	al, 9
$$1:	ret
endp

proc	digit	near			; check if al is a decimal digit
	assume	cs:umm

	sub	al, '0'
	cmp	al, 9
	ret
endp

proc	hexaf	near			; check if al is in the range 'a'-'f'
	assume	cs:umm

	or	al, 32
	sub	al, 'a'
	cmp	al, 5
	ret
endp

proc	error	near			; write an error message
	assume	cs:umm, ds:umm

	mov	ah, 9
	push	dx
	mov	dx, offset eError	; write error prefix
	int	21h
	pop	dx			; specify which error
	int	21h
	ret
endp

proc	newumb	near			; create a new UMB
	assume	cs:umm

	push	ds
	push	es
	mov	dx, cs			; get paragraph address of freep
	mov	ax, offset freep
	shr	ax, 4
	add	dx, ax
	mov	es, dx

$$1:	cmp	[mbes.nxt], bx		; step through linked list
	ja	short $$2
	mov	es, [mbes.nxt]
	jmp	short $$1

$$2:	push	es			; memory test
	push	bx
	push	cx
	push	di
	mov	dx, cx
 	mov	ax, 0a396h		; test pattern

$$3:	mov	es, bx			; write test pattern to page
	mov	cx, 2048
	xor	di, di
	rep	stosw
	mov	cx, 2048		; read test pattern from page
	xor	di, di
	repe	scasw
	jne	short $$4
	inc	bh			; move to next page
	dec	dx
	jnz	short $$3
	jmp	short $$5

$$4:	add	sp, 14			; error in memory test
	pop	ds
	les	di, [ds:rhptr]
	mov	dx, offset eNoMem
	jmp	abort

$$5:	pop	di			; memory tested ok
	pop	cx
	pop	bx
	pop	es
	xchg	cl, ch			; convert pages to paragraphs
	dec	cx
	mov	ds, bx
	mov	ax, [mbes.nxt]		; link UMB into list
	mov	[mbds.nxt], ax
	mov	[mbes.nxt], bx
	mov	[mbds.siz], cx		; initialize UMB
	mov	[mbds.mlo], 4c4bh
	mov	[mbds.mhi], 0047h
	pop	es
	pop	ds
	ret
endp

eHello	db	'Upper Memory Manager v0.3', 13, 10
	db	'Copyright (C) 1991  Kenneth Gober'
eNL	db	13, 10, 13, 10, '$'
eError	db	'Error installing UMM:  $'
eNoXMS	db	'XMS driver not found', 13, 10, '$'
eLexer	db	'Invalid arguments', 13, 10, '$'
eNoMem	db	'Memory test failed', 13, 10, '$'

	align	4			; start stack on dword boundary

stkbot	db	512 dup (?)		; initialization stack
label	stktop	word

ends	umm0
	end
