;-----------------------------------------------------------
; VM20 - a product of the Tiny Software Foundation, 4/97
;-----------------------------------------------------------
; Optimized virtual memory manager, XMS memory / Disk space
; Written by Tylisha C. Andersen and Tenie Remmel of T.S.F.
; Based on the original VM manager (by Tylisha C. Andersen)
; New implementation with a 16-line cache buffer has better
; speed, supports the 80186 CPU, and allows odd addresses.
; This module is 734 bytes in size (554 w/o random access).
;
; This code is hereby released into the public domain -- no
; restrictions are placed on use, copying or distribution.
;-----------------------------------------------------------

RANDOM_ACCESS   equ 1

O           equ   <offset>
B           equ   <byte ptr>
W           equ   <word ptr>
D           equ   <dword ptr>

public      VMproc

.model tiny
.186
.code

;-----------------------------------------------------------

data_block:                         ; data block entry point
                                    ; DO NOT move these around

$xms_call   dd    0                 ; xms call point        +0
$xms_size   dw    0                 ; xms length in blocks  +4
$xms_handle dw    0                 ; xms handle            +6
$cache_seg  dw    0                 ; seg. of 64K cache     +8
$fhandle    dw    0                 ; temp-file handle      +10

$line_ptrs  dw    16 dup(0)         ; position of blocks    +12
$line_ctrs  db    16 dup(0)         ; block usage counters  +44
$line_flgs  db    16 dup(0)         ; change flags          +60

$fname      db    'C:\$VM$.TMP',0   ; temp-file name        +76

if RANDOM_ACCESS
$buffer_seg dw    0                 ; seg. of copy buffer   +88
endif

;-----------------------------------------------------------

xms_call    equ   [bp]
xms_size    equ   [bp+4]
xms_handle  equ   [bp+6]
cache_seg   equ   [bp+8]
fhandle     equ   [bp+10]
line_ptrs   equ   [bp+12]
line_ctrs   equ   [bp+44]
line_flgs   equ   [bp+60]
fname       equ   [bp+76]
buffer_seg  equ   [bp+88]
xms_struc   equ   [bp+90]

;-----------------------------------------------------------
; VMproc:  VM call point
;-----------------------------------------------------------
; ah = 0:  init
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; ah = 1:  close
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; ah = 2:  block read
;     ds:dx = buffer
;     bx = block number
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; ah = 3:  block write
;     ds:dx = buffer
;     bx = block number
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; ah = 4:  random read
;     ds:dx = buffer
;     si:bx = offset in VM
;     cx = length
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; ah = 5:  random write
;     ds:dx = buffer
;     si:bx = offset in VM
;     cx = length
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; returns cf = error flag
;-----------------------------------------------------------

VMproc      proc

            pusha                   ; save all registers
            push  ds
            push  es

            sub   sp, 106           ; set up stack frame
            mov   bp, sp

            pusha                   ; save all registers
            push  ss                ; es = ss
            pop   es
            mov   si, O(data_block) ; si = data block pointer
            mov   di, bp            ; di = stack space
            mov   cx, 90/2          ; 90 bytes
            db    02Eh              ; cs-segment override
            rep   movsw             ; copy data block to stack
            popa                    ; restore registers

;-------------------------------------------------


            shr   ax, 8             ; al = service, ah = 0
            jz    VM_init           ; 0 = init
            cmp   W fhandle, 0      ; not initialized yet?
            je    VM_error

VM_c1:      cmp   al, 1             ; 1 = close
            ja    VM_c23
            jmp   VM_close
VM_c23:     cmp   al, 3             ; 2,3 = block read/write
            ja    VM_c4
            jmp   VM_block
VM_c4:

if RANDOM_ACCESS
            cmp   al, 4             ; 4 = random read
            ja    VM_c5
            jmp   VM_rread
VM_c5:      cmp   al, 5             ; 5 = random write
            ja    VM_error
            jmp   VM_rwrite
endif

VM_error:   stc                     ; invalid, exit with error
            jmp   VM_done

;-------------------------------------------------

VM_init:    push  ss                ; ds = ss
            pop   ds

            xchg  cx, ax            ; cx = 0
            cmp   W fhandle, cx     ; already initialized?
            jne   VM_error

            mov   ah, 48h           ; try to allocate memory
if RANDOM_ACCESS
            mov   bx, 1100h         ; 68K with random access
else
            mov   bx, 1000h         ; 64K w/o random access
endif
            int   21h               ; DOS call
            jc    VM_error          ; jump if error

            mov   cache_seg, ax     ; set cache segment (64K)
            mov   es, ax
if RANDOM_ACCESS
            add   ax, 1000h         ; set buffer segment (4K)
            mov   buffer_seg, ax
endif

            mov   ah, 3Ch           ; create file, normal attr
            lea   dx, fname         ; dx = filename
            int   21h               ; DOS call
            jnc   VMin_fok          ; jump if no error

            mov   ah, 49h           ; error opening file, free memory
            int   21h               ; es = cache_seg at this point
            jmp   VM_error          ; exit with error

VMin_fok:   mov   fhandle, ax       ; set file handle

            mov   ax, 15            ; 16 cache lines
VMin_loop:  mov   si, ax            ; set line's usage counter
            mov   line_ctrs[si], al ; and change-flag
            mov   line_flgs[si], ah
            add   si, si            ; set line's position pointer
            mov   line_ptrs[si], ax
            dec   ax                ; next line
            jnl   VMin_loop         ; loop

            mov   ax, 4300h         ; check for xms
            int   2Fh
            test  al, al
            jz    VMin_noxms

            mov   al, 10h           ; get xms call point
            int   2Fh
            mov   xms_call, bx
            mov   xms_call[2], es

            mov   ah, 8             ; get free xms size
            call  D xms_call
            test  ax, ax            ; no xms memory free?
            jz    VMin_noxms

            mov   si, ax            ; si = xms size
            xchg  dx, ax            ; allocate all xms
            mov   ah, 9
            call  D xms_call
            test  ax, ax            ; didn't work?
            jz    VMin_noxms

            shr   si, 2             ; si = xms size in blocks
            mov   xms_size, si
            mov   xms_handle, dx    ; set xms handle
            clc                     ; return with no error
            jmp   VM_done

VMin_noxms: cbw                     ; xms error - set xms size 0
            mov   xms_size, ax      ; al, cf = 0 at this point
            jmp   VM_done           ; return

;-------------------------------------------------

VM_close:   push  ss                ; ds = ss
            pop   ds

            les   dx, D xms_handle  ; dx = xms_handle
                                    ; es = cache_seg

            xor   cx, cx            ; cx = 0
            cmp   W xms_size, cx    ; if xms is being used, then
            je    VMcl_1

            mov   ah, 0Ah           ; free the xms buffer
            call  D xms_call

VMcl_1:     mov   bx, fhandle       ; bx = file handle

            mov   ax, 4200h         ; seek to the beginning
            xor   dx, dx            ; cx = 0 at this point
            int   21h
            mov   ah, 40h           ; truncate (write 0 bytes)
            int   21h

            mov   ah, 3Eh           ; close the file
            int   21h

            mov   fhandle, cx       ; zero the file handle

            lea   dx, fname         ; dx = filename
            mov   ah, 41h           ; delete the file
            int   21h

            mov   ah, 49h           ; free the memory buffer
            int   21h               ; es = cache_seg at this point
            jmp   VM_done           ; return

;-------------------------------------------------

VM_block:   push  ax                ; push the service code

            push  ss                ; es:di = line_ptrs
            pop   es
            lea   di, line_ptrs
            mov   cx, 16            ; search for position
            mov   ax, bx            ; ax = position
            repne scasw
            mov   si, di            ; si = end position
            je    VMb_xgood         ; if it's not in the cache, then

VMb_xbad:   mov   cl, 16            ; di now = line_ctrs
            mov   ax, 15            ; find oldest block
            repne scasb
            xchg  di, ax            ; di = oldest block
            sub   di, cx
            imul  si, di, 2         ; si = block number * 2

            cmp   line_flgs[di], ch ; if block is dirty, then
            je    VMb_nosave
            mov   ah, 1             ; save this block
            call  VM_cacheb

VMb_nosave: mov   line_ptrs[si], bx ; set block pointer

            pop   ax                ; get service code
            push  ax
            cmp   al, 2             ; if it is a read, then
            jne   VMb_cont1
            call  VM_cacheb         ; load this block (ah = 0)
            jmp   VMb_cont1         ; if it is in the cache, then

VMb_xgood:  lea   ax, line_ptrs+2   ; si = block number * 2
            sub   si, ax

VMb_cont1:  mov   line_ptrs[si], bx ; set block pointer
            shr   si, 1             ; si = block number
            mov   al, line_ctrs[si] ; get counter for this block

            mov   di, 15            ; 16 blocks
VMb_loop:   cmp   line_ctrs[di], al ; add 1 if less than the current
            adc   line_ctrs[di], ch ; block's counter value
            dec   di                ; next block
            jnl   VMb_loop          ; loop

            mov   line_ctrs[si], ch ; set counter for this block to 0

            pop   ax                ; check service code
            cmp   al, 2
            mov   bx, cache_seg     ; bx = cache segment
            jne   VMb_write         ; if it is a read, then:

VMb_read:   push  ds                ; es:di = output buffer
            pop   es
            mov   di, dx
            mov   ds, bx            ; ds:si = offset of block in cache
            imul  si, 4096
            jmp   VMb_cont2         ; otherwise, it is a write:

VMb_write:  mov   line_flgs[si], al ; block has been changed (al > 0)
            mov   es, bx            ; es:di = offset of block in cache
            imul  di, si, 4096
            mov   si, dx            ; ds:si = input buffer

VMb_cont2:  mov   cx, 2048          ; copy block to output buffer
            rep   movsw
            jmp   VM_done           ; return (cf = 0 from imul)

;-------------------------------------------------
if RANDOM_ACCESS

VM_rread:   push  ds                ; es:di = output buffer
            pop   es
            mov   di, dx

            call  VMr_split         ; split into block number & offset
            jnc   VMrr_short        ; jump if short write

            push  cx                ; save cx
            mov   cx, 4096          ; cx = length to end of block
            sub   cx, si
            call  VMrr_part         ; read first part
            xchg  cx, ax
            pop   cx                ; restore cx

            sub   cx, ax            ; adjust cx
            inc   bx                ; next block

            push  es                ; ds:dx = output buffer
            pop   ds
            mov   dx, di

            mov   ah, 2             ; read in middle
            call  VMr_loop
            mov   di, dx            ; es:di = output buffer
            xor   si, si            ; read last part
VMrr_short: push  offset VM_ndone   ; fall through to VM_ndone

;-------------------------------------------------
; read cx bytes from block bx at si to es:di

VMrr_part:  mov   ds, buffer_seg    ; ds:dx = temporary buffer
            xor   dx, dx
            mov   ah, 2             ; read block
            call  VMproc
            rep   movsb             ; copy data to output buffer
            ret                     ; return

;-------------------------------------------------

VM_rwrite:  call  VMr_split         ; split into block number & offset
            mov   di, si            ; di = offset
            jnc   VMrw_short        ; jump if short write

            push  cx                ; save cx
            mov   cx, 4096          ; cx = length to end of block
            sub   cx, di
            call  VMrw_part         ; write part of block
            xchg  cx, ax
            pop   cx                ; restore cx

            sub   cx, ax            ; adjust cx
            inc   bx                ; next block

            mov   ah, 3             ; write out middle
            call  VMr_loop
            xor   di, di            ; write last part
VMrw_short: push  offset VM_ndone   ; fall through to VM_ndone

;-------------------------------------------------
; write cx bytes from ds:dx to block bx at di

VMrw_part:  push  ds                ; save registers
            push  ds                ; save ds
            mov   si, dx            ; si = dx
            mov   ds, buffer_seg    ; ds:dx = temporary buffer
            xor   dx, dx
            mov   ah, 2             ; read block
            call  VMproc

            push  ds                ; es:di = temporary buffer
            pop   es
            pop   ds                ; ds:si = input buffer
            rep   movsb             ; copy data to temporary buffer

            push  es                ; ds:dx = temporary buffer
            pop   ds
            mov   ah, 3             ; write block
            call  VMproc
            pop   ds                ; restore registers
            mov   dx, si            ; dx = new position in buffer
            ret                     ; return

;-------------------------------------------------
; split VM offset into block number & offset
; in:  si:bx = VM offset, cx = length
; out: bx = block, si = offset, carry set if long

VMr_split:  mov   ax, bx            ; ax = low word of offset
            shr   bx, 12            ; bx = block number
            shl   si, 4
            or    bx, si
            mov   si, 0FFFh         ; si = offset in block
            and   si, ax

            mov   ax, cx            ; ax = last byte in block to read
            dec   ax
            add   ax, si
            jc    VMrs_ret          ; carry = long read
            cmp   ax, 1000h         ; set carry if different block
            cmc
VMrs_ret:   ret                     ; return

;-------------------------------------------------
; loop to read/write middle of random buffer
; in:  ds:dx = start, cx = length, ah = command
; out: ds:dx = end, cx = length remaining

VMr_ltop:   call  VMproc            ; read block into output buffer
            inc   bx                ; next block
            add   dx, di
            sub   cx, di            ; reduce length
VMr_loop:   mov   di, 4096          ; any full blocks left?
            cmp   cx, di
            jae   VMr_ltop
            ret                     ; return

endif
;-------------------------------------------------

VM_done:    push  cs                ; es = cs
            pop   es
            mov   si, bp            ; si = stack space
            mov   di, O(data_block) ; di = data block pointer
            mov   cx, 90/2          ; 90 bytes
            db    036h              ; ss-segment override
            rep   movsw             ; copy stack to data block

VM_ndone:   lea   sp, [bp+106]      ; delete stack frame
            pop   es                ; restore registers
            pop   ds
            popa
            ret                     ; return

;-------------------------------------------------
; cache a block - si = block number * 2
; ah = 0:  load block from xms/disk into cache
; ah = 1:  write block from cache to xms/disk
;-------------------------------------------------

VM_cacheb:  pusha                   ; save all registers
            push  ds

            xchg  cx, ax            ; ch = service number
            mov   ax, 4096          ; ax = 4096

            mov   bx, line_ptrs[si] ; bx = physical block number
            shr   si, 1
            mov   line_flgs[si], al ; clear block's change flag
            shl   si, 12            ; si = cache offset

            cmp   bx, xms_size      ; if it is in xms, then
            jae   VMcb_file

            push  ss                ; es:di = xms structure
            pop   es
            lea   di, xms_struc
            push  es                ; save es:di
            push  di

            stosw                   ; set low word of length
            mul   bx                ; dx:ax = offset in xms
            xchg  bx, ax            ; dx:bx = offset in xms
            xor   ax, ax            ; ax = 0
            stosw                   ; set high word of length
            test  ch, ch            ; check service number
            lds   cx, D xms_handle  ; cx = xms handle
                                    ; ds:di = offset in memory

            jz    VMcb_cont         ; if it's a write, then
            xchg  ax, cx            ; switch src, dest blocks
            xchg  di, bx
            push  dx
            push  ds
            pop   dx
            pop   ds

VMcb_cont:  push  ds
            xchg  cx, ax            ; set src handle
            stosw
            xchg  bx, ax            ; set src offset
            stosw
            xchg  dx, ax
            stosw
            xchg  cx, ax            ; set dest handle
            stosw
            xchg  si, ax            ; set dest offset
            stosw
            pop   ax
            stosw

            pop   si                ; ds:si = xms structure
            pop   ds
            mov   ah, 0Bh           ; copy memory to/from xms
            call  D xms_call
            jmp   VMcb_done         ; continue

VMcb_file:  push  cx                ; save service number
            push  ax                ; save ax
            sub   bx, xms_size      ; it is in the file, so
            mul   bx                ; dx:ax = file offset

            xchg  ax, dx            ; cx:dx = file offset
            xchg  ax, cx

            mov   bx, fhandle       ; bx = file handle
            mov   ax, 4200h         ; seek to position of block
            int   21h

            mov   ds, cache_seg     ; ds:dx = cache block
            mov   dx, si
            pop   cx                ; cx = 4096 bytes
            pop   ax                ; ah = 3Fh (read), 40h (write)
            add   ah, 3Fh
            int   21h

VMcb_done:  pop   ds                ; restore registers
            popa
            ret                     ; return

VMproc      endp

end
