Paul Thomson is a software engineer at Black Dot. He can be reached at 439 N Lake Shore Dr., Palatine, IL 60067.
The PC AT BIOS contains a service -- service 87h of interrupt 15h -- forcopying memory in protected mode, thereby allowing you to copy memory anywhere in the 16- Mbyte address range of the 80286. This means that you can copy memory from real-mode memory to extended memory and back again. Before calling this service routine, however, you must set up a data structure for protected-mode operation. This structure is called a global descriptor table. It contains six descriptors, each with 8 bytes. Two of these are segment descriptors that describe the source and target buffers. The rest of the descriptors are simply dummy descriptors that will be initialized by the BIOS routine. The 8-byte segment descriptors are shown in Table 1, below.
Table 1: Segment descriptor formats
Descriptor Format ------------------------ 16 bits Null: Reserved for future use 8 bits Access rights byte: Contains attribute flags for the segment (writable?, data segment?, etc.) 24 bits Physical address: Any real mode address must be converted to a 24 bit physical address by the following formula: (segreg * 16) + offset. 16 bits Segment length in bytes: Words to transfer * 2.
Before calling the service routine, you must set up the registers as shown in Table 2.
Table 2: Setup for registers
AH = 87h ES:SI = Real mode address of global descriptor table CX = Count of words to copy
There is also another interrupt 15h service -- service 88h -- which is useful for extended memory. Service 88h returns the number of Kbytes of extended memory (which starts at 0x100000) in AX. Since service 88h will not return an error when accessing non-existent memory, service 88h can be used to determine if the move will involve valid memory locations.
Listing One, page 108, shows a Microsoft C (compact or large model) version of the move routine (movphy), the top of memory routine (getext), and a test routine (main) while Listing Two, page 119, is an assembly language version. movphy will copy memory in 64K-byte chunks given the source and destination addresses in 24-bit format. getext, which must be called once (before movphy is called for the first time) sets up a static variable with the maximum physical address of extended memory. The test copies a string between extended and real-mode memory to show that the routine is working correctly. An error can be discovered by checking the routine return code (ax in C, ah in the assembler routine). Before you use the program, check that your extended memory is not occupied by vdisk; the program can wipe out your virtual disk files and/or render vdisk unusable.
_USING EXTENDED MEMORY ON THE PC AT_
by Paul Thomson
[LISTING ONE]
Copyright © 1989, Dr. Dobb's Journal<a name="0041_0007">
/***
*** ROUTINE FOR COPYING MEMORY USING PHYSICAL ADDRESSES ON THE PCAT.
*** WRITTEN BY PAUL THOMSON.
*** COMPILE USING MICROSOFT C V4.0, LARGE OR COMPACT MODEL.
*** DO NOT USE THIS ROUTINE WITH A VIRTUAL DISK IN EXTENDED MEMORY.
***/
#include <stdio.h>
#include <dos.h>
#define PHYS(s) (((long)FP_SEG(s) << 4) + FP_OFF(s)) /* CALC PHYS ADDR */
#define BUFSZ 16 /* BUF SIZE IN BYTES */
#define PHY_ADDR 0x100000L /* BEG OF EXT MEMORY */
char *buf="ORIGINAL MESSAGE";
/* TEST MOVPHY BY COPYING A MESSAGE TO EXTENDED MEMORY AND BACK */
main()
{
extsize();
puts(buf);
movphy(PHY_ADDR, PHYS(buf), BUFSZ/2); /* MOVE TO EXTENDED MEMORY */
sprintf(buf, "XXXXXXXXXXXXXXXX"); /* OVERWRITE BUFFER */
puts(buf);
movphy(PHYS(buf), PHY_ADDR, BUFSZ/2); /* MOVE BACK FROM EXTENDED MEMORY */
puts(buf);
}
static long maxext; /* HOLDS MAX ADDRESS OF EXTENDED MEMORY */
extsize()
{
union REGS r;
r.h.ah = 0x88;
int86(0x15, &r, &r); /* RETURNS SIZE OF EXTENDED MEM IN KBYTES */
maxext = (r.x.ax + 1024L)*1024; /* FIND TOP OF EXTENDED MEMORY */
}
/* MOVE MEMORY USING PHYSICAL ADDRESSES */
movphy(target, source, wcount)
unsigned long target; /* PHYSICAL 24 BIT TARGET ADDRESS */
unsigned long source; /* PHYSICAL 24 BIT SOURCE ADDRESS */
int wcount; /* 16 BIT COUNT OF WORDS TO MOVE 0 - 32767 */
{
int bcount;
char gdt[48]; /* GLOBAL DESCRIPTOR TABLE (6 DESCRIPTORS*8) */
char *g = gdt; /* POINTER TO gdt FOR MACROS FP_SEG & FP_OFF */
union REGS r; /* HOLDS REGISTER VALUES FOR int86x CALL */
struct SREGS s; /* HOLDS SEG REGISTER VALUES FOR int86x CALL */
if(wcount <= 0) /* CHECK FOR WORD COUNT TOO BIG OR 0 */
return(wcount);
bcount = wcount*2; /* SIZE IN BYTES TO MOVE */
if(target+bcount >= maxext || source+bcount >= maxext)
return(4);
/* DESCRIPTORS 0 AND 1 ARE DUMMIES (NULL) */
gdt[0]=gdt[1]=gdt[2]=gdt[3]=gdt[4]=gdt[5]=gdt[6]=gdt[7]=0;
gdt[8]=gdt[9]=gdt[10]=gdt[11]=gdt[12]=gdt[13]=gdt[14]=gdt[15]=0;
/* DESCRIPTOR 2: SOURCE */
gdt[16] = bcount; /* BYTE COUNT */
gdt[17] = bcount>>8;
gdt[18] = source; /* PHYSICAL ADDRESS TO COPY FROM */
gdt[19] = source>>8;
gdt[20] = source>>16;
gdt[21] = 0x93; /* ACCESS RIGHTS BYTE */
gdt[22] = gdt[23] = 0; /* UNUSED ENTRIES */
/* DESCRIPTOR 3: TARGET */
gdt[24] = bcount; /* BYTE COUNT */
gdt[25] = bcount>>8;
gdt[26] = target; /* PHYSICAL ADDRESS TO COPY TO */
gdt[27] = target>>8;
gdt[28] = target>>16;
gdt[29] = 0x93; /* ACCESS RIGHTS BYTE */
gdt[30] = gdt[31] = 0; /* UNUSED ENTRIES */
/* DESCRIPTORS 4 AND 5 ARE DUMMIES (NULL) */
gdt[32]=gdt[33]=gdt[34]=gdt[35]=gdt[36]=gdt[37]=gdt[38]=gdt[39]=0;
gdt[40]=gdt[41]=gdt[42]=gdt[43]=gdt[44]=gdt[45]=gdt[46]=gdt[47]=0;
r.h.ah = 0x87; /* AH = SERVICE 0x87 */
r.x.cx = wcount; /* CX = COUNT OF WORDS TO TRANSFER */
s.es = FP_SEG(g); /* ES:SI = SEGMENT:OFFSET OF GDT */
r.x.si = FP_OFF(g);
int86x(0x15, &r, &r, &s); /* PERFORM BIOS INTERRUPT 0x15 */
return(r.h.ah);
/* RETURN STATUS:
0: SUCCESSFUL MOVE
1: RAM PARITY ERROR
2: EXCEPTION ERROR
3: ADDRESS LINE 20 FAILED
4: MEMORY OUT OF RANGE
<0: WORD COUNT > 32767 */
}
<a name="0041_0008"><a name="0041_0008">
<a name="0041_0009">
[LISTING TWO]<a name="0041_0009">
; ROUTINE FOR COPYING MEMORY USING PHYSICAL ADDRESSES ON THE PCAT.
; WRITTEN BY PAUL THOMSON.
; DO NOT USE THIS ROUTINE WITH A VIRTUAL DISK IN EXTENDED MEMORY.
.286p ; ALLOW 286 INSTRUCTIONS
code segment
assume cs:code,ds:code,es:code,ss:code
; TEST MOVPHY BY COPYING A MESSAGE TO EXTENDED MEMORY AND BACK
test proc near
mov ax,cs ; ALLOW ACCESS OF DATA IN CODE SEG
mov ds,ax
call extsize ; FIND TOP OF EXTENDED MEMORY
mov dx,offset mess1 ; PRINT MESSAGE
mov ah,9
int 21h
mov dx,cs ; CALCULATE PHYS ADDR FROM REAL ADDR OF MESSAGE BUF
shr dx,12 ; SI = BITS 0-15, DL = BITS 16-23
mov ax,cs
shl ax,4
mov si,offset mess1
add si,ax
adc dl,0
push si ; SAVE PHYS ADDRESS FOR LATER
push dx
mov dh,10h ; TOP OF EXTENDED MEMORY (100000h)
mov di,0 ; DI = BITS 0-15, DH = 16-23
mov cx,8 ; SIZE OF MESSAGE BUF IN WORDS
call movphy ; MOVE MESSAGE TO EXTENDED MEMORY
sub bx,bx ; OVERWRITE MESSAGE BUFFER
top:
mov al,mess2[bx]
mov mess1[bx],al
inc bx
cmp bx,16
jl top
mov dx,offset mess1
mov ah,9 ; PRINT OVERWRITTEN MESSAGE BUFFER
int 21h
pop dx ; GET PHYS BUFFER ADDRESS FROM BEFORE
pop di ; DI = BITS 0-15, DH = BITS 16-23
mov dh,dl
mov dl,10h ; TOP OF EXTENDED MEMORY (100000h)
mov si,0 ; DI = BITS 0-15, DH = 16-23
mov cx,8 ; SIZE OF MESSAGE BUF IN WORDS
call movphy ; MOVE MESSAGE BACK FROM EXTENDED MEMORY
mov dx,offset mess1 ; PRINT RESTORED MESSAGE
mov ah,9
int 21h
mov ah,4ch ; EXIT
int 21h
test endp
mess1 db 'ORIGINAL MESSAGE',0dh,0ah,'$'
mess2 db 'XXXXXXXXXXXXXXXX'
; extsize - GET PHYSICAL ADDRESS OF TOP OF EXTENDED MEMORY
; ADDRESS RETURNED IN max_hi,max_lo
extsize proc near
mov ah,88h
int 15h
mov cx,1024
add ax,cx
mul cx
mov max_hi,dl
mov max_lo,ax
ret
extsize endp
max_hi db ?
max_lo dw ?
; movphy - MOVE MEMORY USING PHYSICAL ADDRESSES
; CALLED WITH:
; dh:di = physical 24 bit target address.
; dl:si = physical 24 bit source address.
; cx = word count
; STATUS RETURNED IN ah:
; 0: successful move
; 1: RAM parity error
; 2: exception error
; 3: address line 20 failed
; 4: memory out of range
; 255: word count > 32767
public movphy
movphy proc near
push ds
mov ax,cs ; ALLOW ACCESS OF gdt IN CODE SEGMENT
mov ds,ax
mov es,ax ; es = SEGMENT OF gdt FOR int 15h
mov ax,cx ; CALCULATE MAXIMUM TARGET ADDRESS
shl ax,1
mov bl,dh
add ax,di
adc bl,0
cmp bl,max_hi ; CHECK IF TARGET ADDRESS OUT OF RANGE
jl $target_ok
jg $bad_range
cmp ax,max_lo
jge $bad_range
$target_ok:
mov ax,cx ; CALCULATE MAXIMUM SOURCE ADDRESS
shl ax,1
mov bl,dl
add ax,si
adc bl,0
cmp bl,max_hi ; CHECK IF SOURCE ADDRESS OUT OF RANGE
jl $source_ok
jg $bad_range
cmp ax,max_lo
jl $source_ok
$bad_range:
mov ah,4 ; IF ACCESSING NON-EXISTENT MEMORY, RETURN ERROR 4
jmp $xend
$source_ok:
cmp cx,0 ; CHECK FOR WORD COUNT TOO BIG OR 0
jg $wcount_ok
mov ax,cx ; RETURN 255 IF WORD COUNT > 32767
jmp $xend
$wcount_ok:
; DESCRIPTORS 0,1,4,5 ARE DUMMIES (NULL)
sub ax,ax
mov gdt,ax ; DESCRIPTOR 0
mov gdt+2,ax
mov gdt+4,ax
mov gdt+6,ax
mov gdt+8,ax ; DESCRIPTOR 1
mov gdt+10,ax
mov gdt+12,ax
mov gdt+14,ax
mov gdt+32,ax ; DESCRIPTOR 4
mov gdt+34,ax
mov gdt+36,ax
mov gdt+38,ax
mov gdt+40,ax ; DESCRIPTOR 5
mov gdt+42,ax
mov gdt+44,ax
mov gdt+46,ax
mov gdt+22,ax ; UNUSED ENTRIES IN DESCRIPTOR 2
mov gdt+30,ax ; UNUSED ENTRIES IN DESCRIPTOR 3
mov ax,cx ; CHANGE WORD COUNT TO BYTE COUNT
shl ax,1
mov gdt+16,ax ; BYTE COUNT DESCRIPTOR 2
mov gdt+24,ax ; BYTE COUNT DESCRIPTOR 3
mov gdt+18,si ; PHYSICAL ADDRESS TO COPY FROM
mov al,dl
mov ah,93h ; ACCESS RIGHTS BYTE
mov gdt+20,ax
mov gdt+26,di ; PHYSICAL ADDRESS TO COPY TO
mov al,dh
mov ah,93h ; ACCESS RIGHTS BYTE
mov gdt+28,ax
; MAKE DOS CALL
mov ah,87h ; SELECT SERVICE 87h
mov si,offset gdt ; ES:SI = SEGMENT:OFFSET OF GLOBAL DESCRIPTOR TABLE
int 15h ; PERFORM MEMORY MOVE
$xend:
pop ds
ret
gdt dw 24 dup(?) ; GLOBAL DESCRIPTOR TABLE
movphy endp
code ends
end