'
' NeoLib - EMS Module
'
' Features:
'  6 subs
'  9 functions
' For a total of: 15 routines
'
' Specially designed and coded for AAP's QBCPC
' Official Library of the QuickBASIC Caliber Programming Compo (Summer & Autumn 2003)
'

DECLARE FUNCTION neoEMSexist% ()
DECLARE FUNCTION neoEMSpageFrame% ()
DECLARE FUNCTION neoEMSversion$ ()
DECLARE FUNCTION neoEMSfreePages% ()
DECLARE FUNCTION neoEMStotalPages% ()
DECLARE FUNCTION neoEMSfreeHandles% ()
DECLARE FUNCTION neoEMSisError% ()
DECLARE FUNCTION neoEMSgetErrorMsg$ ()
DECLARE FUNCTION neoEMSalloc% (NoPages AS INTEGER)

DECLARE SUB neoEMSdealloc (Handle AS INTEGER)
DECLARE SUB neoEMSrealloc (Handle AS INTEGER, NoPages AS INTEGER)
DECLARE SUB neoEMSmap (LogicalPage AS INTEGER, PhysicalPage AS INTEGER, Handle AS INTEGER)
DECLARE SUB neoEMSmapX (LogicalOffset AS INTEGER, PhysicalOffset AS INTEGER, NoPages AS INTEGER, Handle AS INTEGER)
DECLARE SUB neoEMSmove (SrcHandle AS INTEGER, SrcSegmentOrPage AS INTEGER, SrcOffset AS INTEGER, DstHandle AS INTEGER, DstSegmentOrPage AS INTEGER, DstOffset AS INTEGER, DataLength AS LONG)
DECLARE SUB neoEMSexchange (SrcHandle AS INTEGER, SrcSegmentOrPage AS INTEGER, SrcOffset AS INTEGER, DstHandle AS INTEGER, DstSegmentOrPage AS INTEGER, DstOffset AS INTEGER, DataLength AS LONG)

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

DIM SHARED Regs AS RegType
DIM SHARED RegsX AS RegTypeX
DIM SHARED EMSerror AS INTEGER

'/////////////////////////////////////////////////////////////
' FUNCTIONS
'/////////////////////////////////////////////////////////////
FUNCTION neoEMSexist
	'checks if an EMM manager is present
	' -1: yes
	'  0: no

	'get the interrupt vector of Int67h
	RegsX.ax = &H3567
	INTERRUPTX &H21, RegsX, RegsX

	'the driver is stored in ES:BX
	DEF SEG = RegsX.es
	DeviceID$ = ""
	FOR I = Regs.bx + 10 TO Regs.bx + 17
		DeviceID$ = DeviceID$ + CHR$(PEEK(I))
	NEXT I

	'check if the required EMM driver is present
	IF DeviceID$ = "EMMXXXX0" THEN
		'it is present
		neoEMSexist = -1
	ELSE
		'it is not present
		neoEMSexist = 0
	END IF
END FUNCTION

FUNCTION neoEMSpageFrame
	'gets the EMS page frame (usually D000h or E000h)

        'do interrupt 67h with ah=41h
        Regs.ax = &H4100
	INTERRUPT &H67, Regs, Regs

	'store error code (ah) in a variable
	EMSerror = (Regs.ax AND &HFF00&) \ &H100

	'regs.bx contains the pageframe
	neoEMSpageFrame = Regs.bx
END FUNCTION

FUNCTION neoEMSversion$
	'retreives the EMM version

	'get ems version using int 67h ah=46h
	Regs.ax = &H4600
	INTERRUPT &H67, Regs, Regs

	'store error code (ah) in a variable
	EMSerror = (Regs.ax AND &HFF00&) \ &H100

	'al contain ems version in the following format:
	' AL map           byte - byte
	'                 major - minor
	Major% = (Regs.ax AND &HF0) \ &H10
	Minor% = (Regs.ax AND &HF)
	neoEMSversion$ = LTRIM$(STR$(Major%)) + "." + LTRIM$(STR$(Minor%))
END FUNCTION

FUNCTION neoEMSfreePages
	'gets the number of free pages

        'use int 67h ah=42h
        Regs.ax = &H4200
        INTERRUPT &H67, Regs, Regs

	'store errorcode (ah) in a var
	EMSerror = (Regs.ax AND &HFF00&) \ &H100

        'bx contains no of free pages
        neoEMSfreePages = Regs.bx
END FUNCTION

FUNCTION neoEMStotalPages
	'gets the total number of pages

	'use int 67h ah=42h
	Regs.ax = &H4200
	INTERRUPT &H67, Regs, Regs

	'store error code in a var
	EMSerror = (Regs.ax AND &HFF00&) \ &H100

	'dx contains no of pages
	neoEMStotalPages = Regs.dx
END FUNCTION

FUNCTION neoEMSfreeHandles
	'returns the number of free ems handles

	'use int 67h ah=4Bh
        Regs.ax = &H4B00
        INTERRUPT &H67, Regs, Regs

        'store error code
        EMSerror = (Regs.ax AND &HFF00&) \ &H100

        'the no of free handles is in bx
        neoEMSfreeHandles = Regs.bx
END FUNCTION

FUNCTION neoEMSisError
	'whether an error had occured
	IF EMSerror THEN neoEMSisError = -1 ELSE neoEMSisError = 0
END FUNCTION

FUNCTION neoEMSgetErrorMsg$
        'get the error message
        msg$ = ""
        SELECT CASE EMSerror
        	CASE 0: msg$ = "no error"
                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 1M"
                CASE &H97: msg$ = "source and destination EMS regions have same handle and overlap"
                CASE &H98: msg$ = "memory source or destination type undefined"
                CASE &H99: msg$ = ""
                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"
        END SELECT
        neoEMSgetErrorMsg$ = msg$
END FUNCTION

FUNCTION neoEMSalloc (NoPages AS INTEGER)
	'allocates a number of pages
	' NoPages = number of pages to allocate
	'Returns: EMS handle

        'use int 67h ah=43h
        Regs.ax = &H4300
        Regs.bx = NoPages
        INTERRUPT &H67, Regs, Regs

        'store error code
        EMSerror = (Regs.ax AND &HFF00&) \ &H100

        'the handle is stored in dx
        neoEMSalloc = Regs.dx
END FUNCTION

'/////////////////////////////////////////////////////////////////////
' SUBS
'/////////////////////////////////////////////////////////////////////
SUB neoEMSdealloc (Handle AS INTEGER)
	'deallocates a previously allocated memory region
	' Handle = the handle of the EMS region to deallocate

	'use int 67h ah=45h
	Regs.ax = &H4500
	Regs.dx = Handle
	INTERRUPT &H67, Regs, Regs

	'store error code
	EMSerror = (Regs.ax AND &HFF00&) \ &H100
END SUB

SUB neoEMSrealloc (Handle AS INTEGER, NoPages AS INTEGER)
	'reallocates an ems region
	' Handle = the old ems region
	' NoPages = number of pages to allocate

	'using int67h ah=51h
	Regs.ax = &H5100
	Regs.dx = Handle
	Regs.bx = NoPages
	INTERRUPT &H67, Regs, Regs

	'store the error code
        EMSerror = (Regs.ax AND &HFF00&) \ &H100
END SUB

SUB neoEMSmap (LogicalPage AS INTEGER, PhysicalPage AS INTEGER, Handle AS INTEGER)
	'maps page into the ems page frame
	' LogicalPage = the logical page to map in the pageframe
	' PhysicalPage = the pageframe-page to map in (0 to 3)
	' Handle = the handle of the EMS region to retrieve the logical page from

        'use int 67h ah=44h
        Regs.ax = &H4400 + PhysicalPage
        Regs.bx = LogicalPage
        Regs.dx = Handle
        INTERRUPT &H67, Regs, Regs

        'store error code
        EMSerror = (Regs.ax AND &HFF00&) \ &H100
END SUB

SUB neoEMSmapX (LogicalOffset AS INTEGER, PhysicalOffset AS INTEGER, NoPages AS INTEGER, Handle AS INTEGER)
	'maps a number of pages in the ems page frame
	' LogicalOffset = the logical page to start mapping with
	' PhysicalOffset = the physical page to start mapping to (0 - 3)
	' NoPages = number of pages to map
	' Handle = the handle of the EMS region to get LogicalPages from

        'create a mapping array
        MapArr$ = ""
        FOR I = 0 TO NoPages - 1
        	MapArr$ = MapArr$ + MKI$(LogicalOffset + I) + MKI$(PhysicalOffset + I)
        NEXT I

        'now use interrupt 67h ah=50h
        RegsX.ax = &H5000
        RegsX.dx = Handle
        RegsX.cx = NoPages
	RegsX.ds = VARSEG(MapArr$)
	RegsX.si = SADD(MapArr$)
	INTERRUPTX &H67, RegsX, RegsX

	'store error code
	EMSerror = (Regs.ax AND &HFF00&) \ &H100
END SUB

SUB neoEMSmove (SrcHandle AS INTEGER, SrcSegmentOrPage AS INTEGER, SrcOffset AS INTEGER, DstHandle AS INTEGER, DstSegmentOrPage AS INTEGER, DstOffset AS INTEGER, DataLength AS LONG)
	'copies memory region from one to another
	' SrcHandle = the source EMS handle or 0 if conventional memory
	' SrcSegmentOrPage = the source EMS page or the source segment if conventional mem
	' SrcOffset = the offset within EMS page or within segment if conventional
	' DstHandle = the destination EMS handle or 0 if conventional memory
	' DstSegmentOrPage = the destination EMS page or segment if conventional
	' DstOffset = the destination offset within EMS page or segment
	' DataLength = the number of bytes to copy

        'first make a copy-data buffer
        CpyDta$ = MKL$(DataLength)
        IF SrcHandle = 0 THEN CpyDta$ = CpyDta$ + CHR$(0) ELSE CpyDta$ = CpyDta$ + CHR$(1)
        CpyDta$ = CpyDta$ + MKI$(SrcHandle) + MKI$(SrcOffset) + MKI$(SrcSegmentOrPage)
        IF DstHandle = 0 THEN CpyDta$ = CpyDta$ + CHR$(0) ELSE CpyDta$ = CpyDta$ + CHR$(1)
        CpyDta$ = CpyDta$ + MKI$(DstHandle) + MKI$(DstOffset) + MKI$(DstSegmentOrPage)

        'now use int 67h with ah=57h
        RegsX.ax = &H5700
        RegsX.ds = VARSEG(CpyDta$)
        RegsX.si = SADD(CpyDta$)
        INTERRUPTX &H67, RegsX, RegsX

        'store error code
        EMSerror = (Regs.ax AND &HFF00&) \ &H100
END SUB

SUB neoEMSexchange (SrcHandle AS INTEGER, SrcSegmentOrPage AS INTEGER, SrcOffset AS INTEGER, DstHandle AS INTEGER, DstSegmentOrPage AS INTEGER, DstOffset AS INTEGER, DataLength AS LONG)
	'exchanges memory region from one to another
	' SrcHandle = the source EMS handle or 0 if conventional memory
	' SrcSegmentOrPage = the source EMS page or the source segment if conventional mem
	' SrcOffset = the offset within EMS page or within segment if conventional
	' DstHandle = the destination EMS handle or 0 if conventional memory
	' DstSegmentOrPage = the destination EMS page or segment if conventional
	' DstOffset = the destination offset within EMS page or segment
	' DataLength = the number of bytes to exchange

        'first make a copy-data buffer
        CpyDta$ = MKL$(DataLength)
        IF SrcHandle = 0 THEN CpyDta$ = CpyDta$ + CHR$(0) ELSE CpyDta$ = CpyDta$ + CHR$(1)
        CpyDta$ = CpyDta$ + MKI$(SrcHandle) + MKI$(SrcOffset) + MKI$(SrcSegmentOrPage)
        IF DstHandle = 0 THEN CpyDta$ = CpyDta$ + CHR$(0) ELSE CpyDta$ = CpyDta$ + CHR$(1)
        CpyDta$ = CpyDta$ + MKI$(DstHandle) + MKI$(DstOffset) + MKI$(DstSegmentOrPage)

        'now use int 67h with ah=57h
        RegsX.ax = &H5701
        RegsX.ds = VARSEG(CpyDta$)
        RegsX.si = SADD(CpyDta$)
        INTERRUPTX &H67, RegsX, RegsX

        'store error code
        EMSerror = (Regs.ax AND &HFF00&) \ &H100
END SUB

