' Using EMS in QuickBASIC: Part 1 of 3
' Source Code
'
' By Jon Petrosky (Plasma)
' www.phatcode.net
'
' [Remember to start QB with the /L switch to enable interrupts]

DEFINT A-Z
'$DYNAMIC
'$INCLUDE: 'QB.BI'

DIM SHARED Regs AS RegTypeX
DIM SHARED EMS.Error            'Holds the error code of the last operation

DECLARE FUNCTION EMS.ErrorMsg$ ()
DECLARE FUNCTION EMS.Init ()
DECLARE FUNCTION EMS.Version$ ()
DECLARE FUNCTION EMS.PageFrame ()
DECLARE FUNCTION EMS.FreeHandles ()
DECLARE FUNCTION EMS.FreePages ()
DECLARE FUNCTION EMS.TotalPages ()
DECLARE FUNCTION EMS.AllocPages (NumPages)
DECLARE SUB EMS.DeallocPages (Handle)
DECLARE SUB EMS.MapPage (Physical, Logical, Handle)
DECLARE SUB EMS.MapXPages (PhysicalStart, LogicalStart, NumPages, Handle)
DECLARE SUB EMS.CopyMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)
DECLARE SUB EMS.ExchMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)

CLS

IF NOT EMS.Init THEN
  PRINT "No EMM detected."
  END
END IF

COLOR 14, 1
PRINT SPACE$(22); "Using EMS in QuickBasic: Part 1 of 3"; SPACE$(22)
COLOR 15, 0
PRINT STRING$(31, 196); " EMS Information "; STRING$(32, 196)
COLOR 7
PRINT "EMM Version: "; EMS.Version$

IF EMS.Version$ < "4.0" THEN
  PRINT
  PRINT "EMM 4.0 or later must be present to use some of the EMS functions."
  END
END IF

PRINT "Page frame at: "; HEX$(EMS.PageFrame); "h"
PRINT "Free handles:"; EMS.FreeHandles

IF EMS.FreeHandles = 0 THEN
  PRINT
  PRINT "You need at least one free handle to run this demo."
  END
END IF

PRINT "Total EMS:"; EMS.TotalPages; "pages /"; EMS.TotalPages * 16&; "KB /"; EMS.TotalPages \ 64; "MB"
PRINT "Free EMS:"; EMS.FreePages; "pages /"; EMS.FreePages * 16&; "KB /"; EMS.FreePages \ 64; "MB"

IF EMS.FreePages < 64 THEN
  PRINT
  PRINT "You need at least 64 pages (1 MB) free EMS to run this demo."
  END
END IF

PRINT
COLOR 15, 0
PRINT STRING$(31, 196); " Allocation Test "; STRING$(32, 196)
COLOR 7
PRINT "Allocating 64 pages (1 MB) of EMS...";

Handle = EMS.AllocPages(64)
IF EMS.Error THEN
  PRINT "error!"
  PRINT EMS.ErrorMsg$
  END
ELSE
  PRINT "ok!"
END IF

PRINT "Pages allocated to handle"; Handle
PRINT
COLOR 15, 0
PRINT STRING$(30, 196); " Page Map/Copy Test "; STRING$(30, 196)
COLOR 7
PRINT "Mapping logical page 0 to physical page 0...";

EMS.MapPage 0, 0, Handle
IF EMS.Error THEN
  PRINT "error!"
  PRINT EMS.ErrorMsg$
  END
ELSE
  PRINT "ok!"
END IF

PRINT "Mapping logical pages 0-3 to physical pages 0-3...";

EMS.MapXPages 0, 0, 4, Handle
IF EMS.Error THEN
  PRINT "error!"
  PRINT EMS.ErrorMsg$
  END
ELSE
  PRINT "ok!"
END IF

PRINT "Copying logical pages 0-31 to logical pages 32-63...";

EMS.CopyMem 512288, Handle, 0, 0, Handle, 32, 0
IF EMS.Error THEN
  PRINT "error!"
  PRINT EMS.ErrorMsg$
  END
ELSE
  PRINT "ok!"
END IF

PRINT "Exchanging logical pages 0-31 with logical pages 32-63...";

EMS.ExchMem 512288, Handle, 0, 0, Handle, 32, 0
IF EMS.Error THEN
  PRINT "error!"
  PRINT EMS.ErrorMsg$
  END
ELSE
  PRINT "ok!"
END IF

PRINT
COLOR 15, 0
PRINT STRING$(22, 196); " 10-Second Speed Test (Please Wait) "; STRING$(22, 196)
COLOR 7

Mapped& = 0
StartTime! = TIMER
DO UNTIL StartTime! + 5 <= TIMER
  EMS.MapXPages 0, 0, 4, Handle
  Mapped& = Mapped& + 4
LOOP

Copied& = 0
StartTime! = TIMER
DO UNTIL StartTime! + 5 <= TIMER
  EMS.CopyMem 512288, Handle, 0, 0, Handle, 32, 0
  Copied& = Copied& + 1
LOOP

PRINT "Pages Mapped/Sec:"; Mapped& \ 5
PRINT "Bytes copied/Sec:"; (Copied& * 512288) \ 5
PRINT
COLOR 15, 0
PRINT STRING$(30, 196); " Deallocation Test "; STRING$(31, 196)
COLOR 7
PRINT "Deallocating 64 pages...";

EMS.DeallocPages (Handle)
IF EMS.Error THEN
  PRINT "error!"
  PRINT EMS.ErrorMsg$
  END
ELSE
  PRINT "ok!";
END IF

KeyPress$ = INPUT$(1)
CLS
END

FUNCTION EMS.AllocPages (NumPages)

  'Allocates the number of pages in [NumPages] and
  'returns the EMS handle the memory is allocated to.

  Regs.ax = &H4300                           'Allocate [NumPages] pages of EMS
  Regs.bx = NumPages
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code

  EMS.AllocPages = Regs.dx                   'Return the handle

END FUNCTION

SUB EMS.CopyMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)

  'Copies memory from EMS or base memory to EMS or base memory, where:
  '
  'Length&    = Length of memory to copy in bytes
  'SrcHandle  = EMS handle of source memory (use 0 if source is base memory)
  'SrcSegment = Segment of source memory (or page number if source is EMS)
  'SrcOffset  = Offset of source memory
  'DstHandle  = EMS handle of destination memory (use 0 if destination is base memory)
  'DstSegment = Segment of destination memory (or page number if destination is EMS)
  'DstOffset  = Offset of destination memory

  'Determine the source and destination memory types by checking the handles
  IF SrcHandle = 0 THEN SrcType$ = CHR$(0) ELSE SrcType$ = CHR$(1)
  IF DstHandle = 0 THEN DstType$ = CHR$(0) ELSE DstType$ = CHR$(1)

  'Create a buffer containing the copy information
  CopyInfo$ = MKL$(Length&) + SrcType$ + MKI$(SrcHandle) + MKI$(SrcOffset) + MKI$(SrcSegment) + DstType$ + MKI$(DstHandle) + MKI$(DstOffset) + MKI$(DstSegment)

  Regs.ax = &H5700                           'Copy the memory region
  Regs.ds = VARSEG(CopyInfo$)                'described in the buffer
  Regs.si = SADD(CopyInfo$)
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code

END SUB

SUB EMS.DeallocPages (Handle)

  'Deallocates the EMS pages allocated the EMS handle [Handle].
  'You MUST remember to call the sub before your program ends
  'if you allocate any memory!

  Regs.ax = &H4500                           'Release the pages allocated to [Handle]
  Regs.dx = Handle
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code

END SUB

FUNCTION EMS.ErrorMsg$

  'Returns a text string describing the error code in EMS.Error.

  SELECT CASE EMS.Error
    CASE &H0: Msg$ = "successful"
    CASE &H80: Msg$ = "internal error"
    CASE &H81: Msg$ = "hardware malfunction"
    CASE &H82: Msg$ = "busy -- retry later"
    CASE &H83: Msg$ = "invalid handle"
    CASE &H84: Msg$ = "undefined function requested by application"
    CASE &H85: Msg$ = "no more handles available"
    CASE &H86: Msg$ = "error in save or restore of mapping context"
    CASE &H87: Msg$ = "insufficient memory pages in system"
    CASE &H88: Msg$ = "insufficient memory pages available"
    CASE &H89: Msg$ = "zero pages requested"
    CASE &H8A: Msg$ = "invalid logical page number encountered"
    CASE &H8B: Msg$ = "invalid physical page number encountered"
    CASE &H8C: Msg$ = "page-mapping hardware state save area is full"
    CASE &H8D: Msg$ = "save of mapping context failed"
    CASE &H8E: Msg$ = "restore of mapping context failed"
    CASE &H8F: Msg$ = "undefined subfunction"
    CASE &H90: Msg$ = "undefined attribute type"
    CASE &H91: Msg$ = "feature not supported"
    CASE &H92: Msg$ = "successful, but a portion of the source region has been overwritten"
    CASE &H93: Msg$ = "length of source or destination region exceeds length of region allocated to either source or destination handle"
    CASE &H94: Msg$ = "conventional and expanded memory regions overlap"
    CASE &H95: Msg$ = "offset within logical page exceeds size of logical page"
    CASE &H96: Msg$ = "region length exceeds 1 MB"
    CASE &H97: Msg$ = "source and destination EMS regions have same handle and overlap"
    CASE &H98: Msg$ = "memory source or destination type undefined"
    CASE &H9A: Msg$ = "specified alternate map register or DMA register set not supported"
    CASE &H9B: Msg$ = "all alternate map register or DMA register sets currently allocated"
    CASE &H9C: Msg$ = "alternate map register or DMA register sets not supported"
    CASE &H9D: Msg$ = "undefined or unallocated alternate map register or DMA register set"
    CASE &H9E: Msg$ = "dedicated DMA channels not supported"
    CASE &H9F: Msg$ = "specified dedicated DMA channel not supported"
    CASE &HA0: Msg$ = "no such handle name"
    CASE &HA1: Msg$ = "a handle found had no name, or duplicate handle name"
    CASE &HA2: Msg$ = "attempted to wrap around 1M conventional address space"
    CASE &HA3: Msg$ = "source array corrupted"
    CASE &HA4: Msg$ = "operating system denied access"
    CASE ELSE: Msg$ = HEX$(EMS.Error) '"undefined error"
  END SELECT

  EMS.ErrorMsg$ = Msg$

END FUNCTION

SUB EMS.ExchMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)

  'Exhanges memory from EMS or base memory to EMS or base memory, where:
  '
  'Length&    = Length of memory to exchange in bytes
  'SrcHandle  = EMS handle of source memory (use 0 if source is base memory)
  'SrcSegment = Segment of source memory (or page number if source is EMS)
  'SrcOffset  = Offset of source memory
  'DstHandle  = EMS handle of destination memory (use 0 if destination is base memory)
  'DstSegment = Segment of destination memory (or page number if destination is EMS)
  'DstOffset  = Offset of destination memory

  'Determine the source and destination memory types by checking the handles
  IF SrcHandle = 0 THEN SrcType$ = CHR$(0) ELSE SrcType$ = CHR$(1)
  IF DstHandle = 0 THEN DstType$ = CHR$(0) ELSE DstType$ = CHR$(1)

  'Create a buffer containing the copy information
  ExchInfo$ = MKL$(Length&) + SrcType$ + MKI$(SrcHandle) + MKI$(SrcOffset) + MKI$(SrcSegment) + DstType$ + MKI$(DstHandle) + MKI$(DstOffset) + MKI$(DstSegment)

  Regs.ax = &H5701                           'Exchange the memory region
  Regs.ds = VARSEG(ExchInfo$)                'described in the buffer
  Regs.si = SADD(ExchInfo$)
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code

END SUB

FUNCTION EMS.FreeHandles

  'Returns the number of free (available) EMS handles.

  Regs.ax = &H4B00                             'Get the # of handles in use
  InterruptX &H67, Regs, Regs
  UsedHandles = Regs.bx

  Regs.ax = &H5402                             'Get the total # of handles
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100    'Store the status code
  TotalHandles = Regs.bx

  EMS.FreeHandles = TotalHandles - UsedHandles 'Subtract to get the # of free handles

END FUNCTION

FUNCTION EMS.FreePages

  'Returns the number of free (available) EMS pages
  '(Multiply by 16 to get the amount free EMS in KB.)

  Regs.ax = &H4200                           'Get the # of free pages
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code
  EMS.FreePages = Regs.bx

END FUNCTION

FUNCTION EMS.Init

  'Returns true (-1) if an EMM is installed
  'or false (0) if an EMM is not installed.

  Regs.ax = &H3567                        'Get the interrupt vector for int 67h
  InterruptX &H21, Regs, Regs
  DEF SEG = Regs.es                       'Point to the interrupt segment
  FOR x = 10 TO 17                        'Store the 8 bytes at ES:0A in EMM$
    EMM$ = EMM$ + CHR$(PEEK(x))
  NEXT
  IF EMM$ <> "EMMXXXX0" THEN
    EMS.Init = 0              'EMM not installed
  ELSE
    EMS.Init = -1             'EMM installed
  END IF

END FUNCTION

SUB EMS.MapPage (Physical, Logical, Handle)

  'Maps the logical EMS page [Logical] (allocated to the handle [Handle])
  'to the physical page [Physical] in the EMS page frame.

  Regs.ax = &H4400 + Physical                'Map the logical page [Logical]
  Regs.bx = Logical                          'to the physical page [Physical]
  Regs.dx = Handle
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code

END SUB

SUB EMS.MapXPages (PhysicalStart, LogicalStart, NumPages, Handle)

  'Maps up to 4 logical EMS pages to physical pages in the page frame, where:
  '
  'PhysicalStart = Physical page first logical page is mapped to
  'LogicalStart  = First logical page to map
  'NumPages      = Number of pages to map (1 to 4)
  'Handle        = EMS handle logical pages are allocated to

  'Create a buffer containing the page information
  FOR x = 0 TO NumPages - 1
    MapInfo$ = MapInfo$ + MKI$(LogicalStart + x) + MKI$(PhysicalStart + x)
  NEXT

  Regs.ax = &H5000                           'Map the pages in the buffer
  Regs.cx = NumPages                         'to the pageframe
  Regs.dx = Handle
  Regs.ds = VARSEG(MapInfo$)
  Regs.si = SADD(MapInfo$)
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code

END SUB

FUNCTION EMS.PageFrame

  'Returns the segment of the EMS page frame

  Regs.ax = &H4100                           'Get the segment of the page frame
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Save the status code
  EMS.PageFrame = Regs.bx

END FUNCTION

FUNCTION EMS.TotalPages

  'Returns the total number of EMS pages
  '(Multiply by 16 to get the total amount of EMS in KB.)

  Regs.ax = &H4200                           'Get the # of total pages
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Store the status code
  EMS.TotalPages = Regs.dx

END FUNCTION

FUNCTION EMS.Version$

  'Returns a string containing the EMM version.
  '(Must be "4.0" or greater to use our routines.)

  Regs.ax = &H4600                           'Get the EMM version
  InterruptX &H67, Regs, Regs
  EMS.Error = (Regs.ax AND &HFF00&) \ &H100  'Save the status code

  Version = Regs.ax AND &HFF                 'Split the version number into
  Major = (Version AND &HF0) \ &H10          'its major and minor counterparts
  Minor = Version AND &HF
  EMS.Version$ = LTRIM$(STR$(Major)) + "." + LTRIM$(STR$(Minor))

END FUNCTION

