1 This file contains 3 files:
\r
2 xmem.h : c include file
\r
3 xmem.asm : low level basic XMS acess
\r
4 xmemc.c : super easy C access via functions like fopen, fread, fwrite
\r
5 xopen, xread, xwrite, xseek etc...
\r
7 FOR DOS REAL mode programs, requires HIMEM.SYS to be loaded in
\r
10 XMEM.H ------------- START --------------------------------------------------
\r
11 #if !defined(_XMEM_H)
\r
14 typedef struct xms_node
\r
16 long start, size, off;
\r
18 struct xms_node *next;
\r
25 unsigned long total;
\r
26 unsigned long avail;
\r
27 unsigned long next_off;
\r
32 #define XMSBLOCK 16384u
\r
33 #define XMSBLOCKSHIFT 14
\r
35 extern void LSHL( unsigned long far *SHLnumber, unsigned short n );
\r
37 extern unsigned short XMS_available( void );
\r
39 extern unsigned short XMSblk_available( void );
\r
41 extern short XMS_alloc(unsigned short rsrvd,
\r
42 unsigned short far *size
\r
44 extern short XMS_dealloc(unsigned short Hdl );
\r
45 extern short XMStoMem(unsigned short Handle, // XMS handle returned by XMS_alloc()
\r
46 unsigned short blk, // which 16k block to copy to
\r
47 unsigned short blkAdr, // offset within 16k block
\r
48 unsigned short Bytes, // bytes to copy
\r
51 extern short MemToXMS(unsigned short Handle,
\r
53 unsigned short blkAdr,
\r
54 unsigned short Bytes,
\r
58 // call these for ease
\r
59 short alloc_xms(unsigned short far *size); // size in 16k blocks
\r
60 // NOTE size is changed to the amount block size was altered by!
\r
61 // normaly this is zero
\r
63 short xms_to_mem(unsigned short handle, void far *p, unsigned long off, unsigned short n);
\r
64 short mem_to_xms(unsigned short handle, void far *p, unsigned long off, unsigned short n);
\r
65 void deinit_xms(void);
\r
66 short init_xms(unsigned short min_blocks);
\r
67 void qfree_xms(xms_node_t *node);
\r
68 xms_node_t *qalloc_xms(unsigned long size);
\r
69 xms_node_t *xms_open(char *file);
\r
70 short xms_read(void far *buffer, unsigned short n, xms_node_t *node);
\r
71 short xms_write(void far *buffer, unsigned short n, xms_node_t *node);
\r
72 long xms_tell(xms_node_t *node);
\r
73 short xms_seek(xms_node_t *node, long off, short whence);
\r
74 void xms_close(xms_node_t *node);
\r
76 extern xms_head_t xms_head;
\r
79 /* ---------------------------------- end of file --------------------- */
\r
81 XMEM.H ------------- END --------------------------------------------------
\r
83 xmem.asm --------------------- START --------------------------------------
\r
84 ;-----------------------------------------------------------------------
\r
106 pwrlolvl_TEXT SEGMENT WORD PUBLIC 'CODE'
\r
108 ASSUME CS:pwrlolvl_TEXT, DS:pwrlolvl_TEXT, ES:pwrlolvl_TEXT
\r
110 SUBTTL (Local Procedure) XMS_setup - find a XMS driver.
\r
114 XMSwordByte LABEL BYTE
\r
130 XMSmainGET XMSmoveSTRUC <>
\r
131 XMSmainPUT XMSmoveSTRUC <>
\r
132 XMSwordGET XMSmoveSTRUC <2,,,,,,OFFSET XMSword>
\r
133 XMSwordPUT XMSmoveSTRUC <2,,,OFFSET XMSword>
\r
135 XMSfunctAdr DW 0, 0
\r
137 ; Don't try to call this from your programs
\r
139 XMS_setup PROC NEAR
\r
145 MOV AX,CS ; Set Data segment to the code segment.
\r
147 MOV [XMSwordGET.DestOffsetX],AX ; Set up the move data structures.
\r
148 MOV [XMSwordPUT.SrcOffsetX],AX ;
\r
150 MOV AX,4300H ; See if a XMS Driver Exists.
\r
154 JNE XMS_setup01 ; Return 0 if not.
\r
156 MOV AX,4310H ; If so, set the driver's function
\r
158 MOV [XMSfunctAdr],BX ;
\r
159 MOV [XMSfunctAdr+2],ES ;
\r
161 MOV AX,1 ; Return 1.
\r
172 SUBTTL LSHL - Shift an unsigned long left
\r
175 ;****************************************************************************
\r
177 ;* Shift an unsigned long integer left n number of bits.
\r
179 ;****************************************************************************
\r
182 ; Stack frame definition for void LSHL( unsigned long *SHLnumber, unsigned n );
\r
207 MOV AX,[BX] ; Get the long integer.
\r
211 SHL AX,1 ; Do the long shift.
\r
215 MOV [BX],AX ; Replace the addressed number.
\r
229 SUBTTL Extended Memory - Stack template for EXTget, EXTput
\r
247 SUBTTL Extended Memory - XMS - Return total XMS memory.
\r
250 ; Use this function to detect wether or not XMS driver installed
\r
252 ; Stack frame definition for unsigned XMS_available( void );
\r
254 ; The total XMS memory available (in 16k blocks) is returned.
\r
256 procname XMS_available
\r
262 CALL XMS_setup ; Ensure XMS memory is set.
\r
264 JZ XMS_available01 ; Return zero if not.
\r
266 MOV AH,08H ; Set the size function code.
\r
267 CALL DWORD PTR CS:[XMSfunctAdr] ; Get the size.
\r
269 JZ XMS_available01 ;
\r
271 MOV AX,DX ; Set available Kbytes.
\r
272 SUB AX,64 ; Subtract out the HMA (HIMEM.SYS bug).
\r
273 JNC XMS_available01 ;
\r
274 XOR AX,AX ; Set zero if underflow.
\r
277 MOV CL,4 ; Divide Kbytes by 16 for blocks.
\r
286 endproc XMS_available
\r
288 SUBTTL Extended Memory - XMS - Return largest block XMS mem.
\r
292 ; Stack frame definition for unsigned XMSblk_available( void );
\r
294 ; The size of the largest block of XMS memory available,
\r
295 ; (in 16Kbyte blocks) is returned.
\r
297 procname XMSblk_available
\r
303 CALL XMS_setup ; Ensure XMS memory is set.
\r
305 JZ XMSblk_available01 ; Return zero if not.
\r
307 MOV AH,08H ; Set the size function code.
\r
308 CALL DWORD PTR CS:[XMSfunctAdr] ; Get the size.
\r
310 JZ XMSblk_available01 ;
\r
312 SUB DX,64 ; Subtract out the HMA (HIMEM.SYS bug).
\r
313 JNC XMSblk_available0X ;
\r
314 XOR DX,DX ; Set zero if underflow.
\r
316 XMSblk_available0X:
\r
318 JBE XMSblk_available01 ;
\r
319 MOV AX,DX ; Set available Kbytes.
\r
321 XMSblk_available01:
\r
322 MOV CL,4 ; Divide Kbytes by 16 for blocks.
\r
331 endproc XMSblk_available
\r
333 SUBTTL Extended Memory - XMS De-allocate a memory block.
\r
337 ; Stack frame definition for int XMS_dealloc( int Hdl );
\r
339 ; Zero is returned if the operation fails, non-zero if success.
\r
341 ; its really important to do this, only other way to recover
\r
342 ; XMS blocks is to re-boot
\r
353 procname XMS_dealloc
\r
361 ; CALL XMS_setup ; Ensure XMS memory is set.
\r
363 ; JZ XMS_dealloc01 ; Return zero if not.
\r
365 MOV DX,xmsdealHdl[BP] ; Get the handle to de-allocate.
\r
368 CALL DWORD PTR CS:[XMSfunctAdr] ; De-allocate it.
\r
377 endproc XMS_dealloc
\r
379 SUBTTL Extended Memory - XMS Allocate a memory block.
\r
383 ; Stack frame definition for int XMS_alloc( unsigned rsrvd, *size );
\r
385 ; rsrved and size are in 16K byte blocks.
\r
386 ; rsrved is mem set aside for EMS, generaly zero
\r
388 ; Zero is returned if the operation fails.
\r
389 ; Block (XMS) handle is returned if success.
\r
391 ; size - is reduced by the amount of XMS memory actually allocated.
\r
415 MOV AX,CS ; Set the data segment to the code
\r
416 MOV DS,AX ; segment.
\r
419 ADD xmsalrsrvd[BP],CX ; Subtract out the HMA (HIMEM.SYS bug).
\r
420 SHL xmsalrsrvd[BP],CL ; Convert reserved blocks to K-bytes.
\r
422 LES DI,xmsalsize[BP] ; Load size address.
\r
424 MOV BX,ES:[DI] ; Get the requested size in blocks.
\r
426 TEST BX,0F000H ; Check for more than 64 Megabytes.
\r
432 SHL BX,CL ; Convert to K-Bytes.
\r
434 JZ XMS_alloc05 ; Return zero if no size requested.
\r
436 ; CALL XMS_setup ; Ensure XMS memory is set.
\r
438 ; JZ XMS_alloc05 ; Return zero if not.
\r
441 MOV AH,08H ; Set to Query Free XMS Memory.
\r
442 CALL DWORD PTR [XMSfunctAdr] ;
\r
444 SUB DX,xmsalrsrvd[BP] ; Subtract out reserved blocks.
\r
445 JB XMS_alloc03 ; Ensure no borrow.
\r
452 CMP AX,68 ; Ensure enough memory to allocate.
\r
456 JB XMS_alloc05 ; Exit if not.
\r
458 CMP BL,80H ; Check for errors.
\r
463 CMP CX,DX ; Check actual against requested size.
\r
465 MOV CX,DX ; Set if actual < requested.
\r
468 MOV DX,CX ; Set requested size.
\r
470 CALL DWORD PTR [XMSfunctAdr] ; Allocate it.
\r
471 DEC AX ; Check for errors.
\r
476 MOV AX,CX ; Convert allocated size in KBytes
\r
477 MOV CL,4 ; to allocated blocks.
\r
480 SUB ES:[DI],AX ; Subtract the blocks allocated.
\r
481 MOV AX,DX ; Set to return the handle.
\r
496 SUBTTL Extended Memory - XMS get, put Stack Frame definition
\r
512 SUBTTL Extended Memory - XMStoMem
\r
517 ; Stack frame definition for int XMStoMem( unsigned Handle,
\r
524 ; XMSmemError is returned if the operation fails, Zero if success.
\r
540 MOV AX,CS ; Set Data Segment to Code Segment.
\r
543 MOV CX,xmsgpBytes[BP] ; Get the number of bytes to transfer.
\r
544 LES BX,xmsgpmemAdr[BP] ; Get the memory address.
\r
545 MOV DX,xmsgpHdl[BP] ; Get the XMS handle.
\r
546 MOV [XMSmainGET.SrcHandle],DX ; Set it in the move structures.
\r
547 MOV [XMSwordGET.SrcHandle],DX ;
\r
550 MOV DI,xmsgpblk[BP] ; Get the block number.
\r
551 SHR DI,1 ; Form the 32 bit XMS address in
\r
555 ADD DX,xmsgpblkAdr[BP] ;
\r
557 TEST CX,1 ; Check for an odd number of bytes
\r
558 JZ XMStoMem02 ; to transfer.
\r
560 DEC CX ; Decrement to an even number of bytes.
\r
562 TEST DX,1 ; Check for an odd XMS address.
\r
565 ; XMS address is odd.
\r
566 ; -------------------
\r
568 MOV [XMSwordGET.SrcOffset],DX ; Set the XMS address.
\r
569 MOV [XMSwordGET.SrcOffsetX],DI ;
\r
571 MOV AH,0BH ; Set the XMS move, function code.
\r
572 MOV SI,OFFSET XMSwordGET ; Set address of the move structure.
\r
575 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
577 DEC AX ; Check for errors.
\r
578 JNZ XMStoMem03 ; Error out if error.
\r
580 MOV AX,[XMSword] ; Get the moved word.
\r
582 MOV ES:[BX],AH ; Move the odd byte to memory.
\r
584 INC BX ; Reset the memory address.
\r
585 ADD DX,2 ; And the XMS address.
\r
587 JMP XMStoMem02 ; Move the block.
\r
591 ; XMS address is even.
\r
592 ; --------------------
\r
594 MOV [XMSwordGET.SrcOffset],DX ; Set the XMS address.
\r
596 MOV [XMSwordGET.SrcOffsetX],DI ;
\r
598 MOV AH,0BH ; Set the XMS move, function code.
\r
599 MOV SI,OFFSET XMSwordGET ; Set address of the move structure.
\r
602 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
604 DEC AX ; Check for errors.
\r
605 JNZ XMStoMem03 ; Error out if error.
\r
607 MOV AX,[XMSword] ; Get the moved word.
\r
610 MOV ES:[BX+DI],AL ; Move the odd byte to memory.
\r
614 JCXZ XMStoMem04 ; Avoid a zero byte move.
\r
616 MOV XMSmainGET.Length,CX ; Set length for the move.
\r
618 MOV XMSmainGET.DestOffset,BX ; Set Memory address.
\r
619 MOV XMSmainGET.DestOffsetX,ES ;
\r
621 MOV XMSmainGET.SrcOffset,DX ; Set XMS address.
\r
622 MOV XMSmainGET.SrcOffsetX,DI ;
\r
624 MOV AH,0BH ; Set the XMS move, function code.
\r
625 MOV SI,OFFSET XMSmainGET ; Set address of the move structure.
\r
627 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
628 DEC AX ; Check for errors.
\r
632 MOV AX,XMSmemError ; Set error code if error.
\r
652 SUBTTL Extended Memory - MemToXMS
\r
657 ; Stack frame definition for int MemToXMS( unsigned Handle,
\r
664 ; XMSmemError is returned if the operation fails, Zero if success.
\r
683 MOV CX,xmsgpBytes[BP] ; Get the number of bytes to transfer.
\r
684 LES BX,xmsgpmemAdr[BP] ; Get the memory address.
\r
685 MOV DX,xmsgpHdl[BP] ; Get the XMS handle.
\r
686 MOV [XMSmainPUT.DestHandle],DX ; Set it in the move structures.
\r
687 MOV [XMSwordPUT.DestHandle],DX ;
\r
688 MOV [XMSwordGET.SrcHandle],DX ;
\r
691 MOV DI,xmsgpblk[BP] ; Get the block number.
\r
692 SHR DI,1 ; Form the 32 bit XMS address in
\r
696 ADD DX,xmsgpblkAdr[BP] ;
\r
698 TEST CX,1 ; Check for an odd number of bytes
\r
699 JZ MemToXMS02 ; to transfer.
\r
701 DEC CX ; Decrement to an even number of bytes.
\r
703 TEST DX,1 ; Check for an odd XMS address.
\r
706 ; XMS address is odd.
\r
707 ; -------------------
\r
709 MOV [XMSwordGET.SrcOffset],DX ; Set the XMS address.
\r
710 MOV [XMSwordGET.SrcOffsetX],DI ;
\r
711 MOV [XMSwordPUT.DestOffset],DX ;
\r
712 MOV [XMSwordPUT.DestOffsetX],DI ;
\r
714 MOV AH,0BH ; Set the XMS move, function code.
\r
715 MOV SI,OFFSET XMSwordGET ; Set address of the move structure.
\r
718 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
720 DEC AX ; Check for errors.
\r
721 JNZ MemToXMS03 ; Error out if error.
\r
723 MOV AH,ES:[BX] ; Get the odd memory byte.
\r
725 MOV [XMSwordByte+1],AH ; Put it in the moved word.
\r
727 MOV AH,0BH ; Set the XMS move, function code.
\r
728 MOV SI,OFFSET XMSwordPUT ; Set address of the move structure.
\r
731 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
733 DEC AX ; Check for errors.
\r
734 JNZ MemToXMS03 ; Error out if error.
\r
736 INC BX ; Reset the memory address.
\r
737 ADD DX,2 ; And the XMS address.
\r
739 JMP MemToXMS02 ; Move the block.
\r
742 ; XMS address is even.
\r
743 ; --------------------
\r
745 MOV [XMSwordGET.SrcOffset],DX ; Set the XMS address.
\r
746 MOV [XMSwordPUT.DestOffset],DX ;
\r
748 MOV [XMSwordGET.SrcOffsetX],DI ;
\r
749 MOV [XMSwordPUT.DestOffsetX],DI ;
\r
751 MOV AH,0BH ; Set the XMS move, function code.
\r
752 MOV SI,OFFSET XMSwordGET ; Set address of the move structure.
\r
755 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
757 DEC AX ; Check for errors.
\r
758 JNZ MemToXMS03 ; Error out if error.
\r
761 MOV AL,ES:[BX+DI] ; Get the odd memory byte.
\r
764 MOV [XMSwordByte],AL ; Set the moved word.
\r
766 MOV AH,0BH ; Set the XMS move, function code.
\r
767 MOV SI,OFFSET XMSwordPUT ; Set address of the move structure.
\r
770 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
772 DEC AX ; Check for errors.
\r
773 JNZ MemToXMS03 ; Error out if error.
\r
776 JCXZ MemToXMS04 ; Avoid a zero byte move.
\r
778 MOV XMSmainPUT.Length,CX ; Set length for the move.
\r
780 MOV XMSmainPUT.SrcOffset,BX ; Set Memory address.
\r
781 MOV XMSmainPUT.SrcOffsetX,ES ;
\r
783 MOV XMSmainPUT.DestOffset,DX ; Set XMS address.
\r
784 MOV XMSmainPUT.DestOffsetX,DI ;
\r
786 MOV AH,0BH ; Set the XMS move, function code.
\r
787 MOV SI,OFFSET XMSmainPUT ; Set address of the move structure.
\r
789 CALL DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
\r
790 DEC AX ; Check for errors.
\r
794 MOV AX,XMSmemError ; Set error code if error.
\r
823 xmem.asm --------------------- END --------------------------------------
\r
825 xmemc.c ---------------------------- START -------------------------
\r
829 Copyright 1994 Alec Russell, ALL rights reserved
\r
830 Permission granted to use as you wish.
\r
832 Slightly higher level xms calls than xmem.asm
\r
838 #include <string.h>
\r
839 #include <malloc.h>
\r
843 xms_head_t xms_head={0}; // set handle to zero
\r
846 /* ---------------------- alloc_xms() ----------------- February 19,1994 */
\r
847 short alloc_xms(unsigned short far *size) // size in 16k blocks
\r
849 return(XMS_alloc(0, size));
\r
852 /* ---------------------- xms_to_mem() ---------------- February 19,1994 */
\r
853 short xms_to_mem(unsigned short handle, void far *p, unsigned long off, unsigned short n)
\r
855 unsigned short block, boff;
\r
857 block=off >> XMSBLOCKSHIFT;
\r
858 boff=off - (block << XMSBLOCKSHIFT);
\r
860 return(XMStoMem(handle, block, boff, n, p));
\r
863 /* ---------------------- mem_to_xms() ---------------- February 19,1994 */
\r
864 short mem_to_xms(unsigned short handle, void far *p, unsigned long off, unsigned short n)
\r
866 unsigned short block, boff;
\r
868 block=off >> XMSBLOCKSHIFT;
\r
869 boff=off - (block << XMSBLOCKSHIFT);
\r
871 return(MemToXMS(handle, block, boff, n, p));
\r
874 /* ---------------------- qalloc_xms() -------------------- March 8,1994 */
\r
875 xms_node_t *qalloc_xms(unsigned long size)
\r
877 xms_node_t *node=NULL;
\r
880 if ( size <= xms_head.avail )
\r
882 // look for existing node
\r
886 if ( t1->used == 0 && t1->size >= size )
\r
897 if ( node == NULL ) // didn't find existing node
\r
899 node=malloc(sizeof(xms_node_t));
\r
906 node->start=xms_head.next_off;
\r
907 xms_head.avail-=size;
\r
908 xms_head.next_off+=size;
\r
909 if ( xms_head.next == NULL )
\r
911 xms_head.next=node;
\r
922 pr2("out of near mem in qalloc_xms");
\r
926 pr2("out of xms mem in qalloc size %lu avail %lu", size, xms_head.avail);
\r
931 /* ---------------------- qfree_xms() --------------------- March 8,1994 */
\r
932 void qfree_xms(xms_node_t *node)
\r
936 if ( xms_head.next )
\r
939 while ( t1 != node && t1 )
\r
947 pr2("ERROR didn't find node qfree");
\r
951 pr2("ATTEMPTED to qfree empty list");
\r
955 /* ---------------------- xms_open() ---------------------- March 8,1994 */
\r
956 xms_node_t *xms_open(char *file)
\r
959 xms_node_t *node=NULL;
\r
964 fp=fopen(file, "rb");
\r
967 node=qalloc_xms(filelength(fileno(fp)));
\r
970 buffer=malloc(4096);
\r
974 while ( (i=fread(buffer, 1, 4096, fp)) )
\r
976 mem_to_xms(xms_head.handle, (char far *)buffer, off+node->start, i);
\r
983 pr2("out of mem in xms_open 1");
\r
989 pr2("ERROR opening %s in xms_open", file);
\r
994 /* ---------------------- xms_read() ---------------------- March 8,1994 */
\r
995 short xms_read(void far *buffer, unsigned short n, xms_node_t *node)
\r
998 if ( node->off >= node->size )
\r
1001 if ( n+node->off > node->size )
\r
1002 n=node->size - node->off;
\r
1004 xms_to_mem(xms_head.handle, buffer, node->start+node->off, n);
\r
1010 /* ---------------------- xms_write() ---------------------- March 8,1994 */
\r
1011 short xms_write(void far *buffer, unsigned short n, xms_node_t *node)
\r
1014 if ( node->off >= node->size )
\r
1017 if ( n+node->off > node->size )
\r
1018 n=node->size - node->off;
\r
1020 mem_to_xms(xms_head.handle, buffer, node->start+node->off, n);
\r
1027 /* ---------------------- xms_tell() ---------------------- March 8,1994 */
\r
1028 long xms_tell(xms_node_t *node)
\r
1033 /* ---------------------- xms_seek() ---------------------- March 8,1994 */
\r
1034 short xms_seek(xms_node_t *node, long off, short whence)
\r
1041 if ( off < 0l || off > node->size )
\r
1048 if ( off > 0l || (node->size + off) < 0l )
\r
1051 node->off=node->size + off;
\r
1055 if ( node->off + off < 0l || node->off + off > node->size )
\r
1065 /* ---------------------- xms_close() --------------------- March 8,1994 */
\r
1066 void xms_close(xms_node_t *node)
\r
1071 /* ---------------------- init_xms() ---------------------- March 8,1994 */
\r
1072 short init_xms(unsigned short min_blocks)
\r
1074 unsigned short blocks;
\r
1076 blocks=XMSblk_available();
\r
1077 if ( blocks >= min_blocks )
\r
1079 memset(&xms_head, 0, sizeof(xms_head_t));
\r
1080 if ( (xms_head.handle=alloc_xms(&blocks)) )
\r
1082 pr2("blocks minus by = %u", blocks);
\r
1083 min_blocks-=blocks;
\r
1084 xms_head.avail=xms_head.total=(unsigned long)min_blocks*XMSBLOCK;
\r
1085 blocks=min_blocks;
\r
1096 /* ---------------------- deinit_xms() -------------------- March 8,1994 */
\r
1097 void deinit_xms(void)
\r
1099 xms_node_t *t1, *t2;
\r
1101 if ( xms_head.handle )
\r
1103 XMS_dealloc(xms_head.handle);
\r
1104 if ( xms_head.next )
\r
1116 memset(&xms_head, 0, sizeof(xms_head_t));
\r
1119 /* --------------------------- end of file ------------------------- */
\r
1123 Not sure how to use this?
\r
1125 call init_xms(x) to allocate a big chunk of xms.
\r
1126 x is in 'blocks' of 16Kb. Pick X big enough to buffer all the files
\r
1127 you want to place in xms.
\r
1129 call xms_open("filename); for each file to be buffered. This copies the file
\r
1132 then use xms_read(), xms_write(), and xms_seek() to read the file from
\r
1133 xms instead of disk.
\r
1135 call deinit_xms() just before exit to clean up.
\r
1137 You can also use the lower level calls directly.
\r
1140 xmemc.c ---------------------------- END -------------------------
\r