NT4/private/mvdm/dpmi/dxdisk.asm

954 lines
29 KiB
NASM
Raw Normal View History

2001-01-01 00:00:00 +01:00
PAGE ,132
TITLE DXDISK.ASM -- Dos Extender Low Level Disk Interface
; Copyright (c) Microsoft Corporation 1988-1991. All Rights Reserved.
;***********************************************************************
;
; DXDISK.ASM -- Dos Extender Low Level Disk Interface
;
;-----------------------------------------------------------------------
;
; This module provides the 286 DOS extender's low level protected-to-
; real mode disk interface. It supports a subset of the BIOS Int 13h
; and DOS Int 25h/26h services.
;
;-----------------------------------------------------------------------
;
; 05/22/89 jimmat Original version
; 18-Dec-1992 sudeepb Changed cli/sti to faster FCLI/FSTI
;
;***********************************************************************
.286p
; -------------------------------------------------------
; INCLUDE FILE DEFINITIONS
; -------------------------------------------------------
.xlist
.sall
include segdefs.inc
include gendefs.inc
include pmdefs.inc
include interupt.inc
IFDEF ROM
include dxrom.inc
ENDIF
include intmac.inc
.list
; -------------------------------------------------------
; GENERAL SYMBOL DEFINITIONS
; -------------------------------------------------------
; -------------------------------------------------------
; EXTERNAL SYMBOL DEFINITIONS
; -------------------------------------------------------
extrn EnterIntHandler:NEAR
extrn LeaveIntHandler:NEAR
extrn EnterRealMode:NEAR
extrn EnterProtectedMode:NEAR
extrn GetSegmentAddress:NEAR
extrn SetSegmentAddress:NEAR
externFP NSetSegmentDscr
extrn FreeSelector:NEAR
extrn AllocateSelector:NEAR
extrn ParaToLDTSelector:NEAR
; -------------------------------------------------------
; DATA SEGMENT DEFINITIONS
; -------------------------------------------------------
DXDATA segment
extrn rgbXfrBuf0:BYTE
extrn rgbXfrBuf1:BYTE
extrn rglpfnRmISR:DWORD
cbSectorSize dw ? ;sector size for target drive
cSectorsTransfered dw ? ;# sectors transfered so far
cSectorsToTransfer dw ? ;# sectors to read/write
cSectorsPerTransfer dw ? ;# sectors to R/W at a time
cSectorsThisTransfer dw ? ;# sectors to R/W this time
lpSectorData dd ? ;far pointer to caller's buffer
lpRmISR dd ? ;real mode int service rtn to invoke
DXDATA ends
; -------------------------------------------------------
; CODE SEGMENT VARIABLES
; -------------------------------------------------------
DXCODE segment
DXCODE ends
DXPMCODE segment
IFNDEF ROM
extrn segDXDataPM:WORD
ENDIF
DXPMCODE ends
; -------------------------------------------------------
subttl INT 13h Mapping Services
page
; -------------------------------------------------------
; INT 13h MAPPING SERVICES
; -------------------------------------------------------
DXPMCODE segment
assume cs:DXPMCODE
; -------------------------------------------------------
; PMIntr13 -- Service routine for the Protect Mode INT 13h
; interface to the real mode BIOS.
;
; Input: Various registers
; Output: Various registers
; Errors:
; Uses: All registers preserved, other than return values
;
; Currently, the following Int 13h services are supported:
;
; ah= 0 - Reset Disk System (no mapping required)
; 1 - Get Disk System Status (no mapping required)
; 2 - Read Sector (mapping required)
; 3 - Write Sector (mapping required)
; 4 - Verify Sector (mapping required)
; 5 - Fromat Track (mapping required)
; 6 - Format Bad Track (no mapping required)
; 7 - Format Drive (no mapping required)
; 8 - Get Drive Parameters (mapping required)
; 9 - Init Fixed Disk Characteristics (no mapping required)
; C - Seek (no mapping required)
; D - Reset Disk System (no mapping required)
; 10 - Get Drive Status (no mapping required)
; 11 - Recalibrate Drive (no mapping required)
; 12 - Controller RAM Diagnostic (no mapping required)
; 13 - Controller Drive Diagnostic (no mapping required)
; 14 - Controller Internal Diagnostic (no mapping required)
; 15 - Get Disk Type (no mapping required)
; 16 - Get Disk Change Status (no mapping required)
; 17 - Set Disk Type (no mapping required)
; 18 - Set Media Type for Format (mapping required)
; 19 - Park Heads (no mapping required)
;
; Functions not listed above will most likely not work properly!
;
; NOTE: several functions take 2 bits of the cylinder number in CL
; if the operation is on a fixed disk. The code currently does
; not account for these bits, and may not work properly if
; the request must be split into smaller operations for real/
; extended memory buffering.
;
assume ds:NOTHING,es:NOTHING,ss:NOTHING
public PMIntr13
PMIntr13 proc near
cld ;cya...
call EnterIntHandler ;build an interrupt stack frame
assume ds:DGROUP,es:DGROUP ; also sets up addressability
FSTI ;allow HW interrupts
call IntEntry13 ;perform translations/buffering
; Execute the real mode BIOS routine
push es
assume es:nothing
mov ax,SEL_RMIVT OR STD_RING
mov es,ax
mov ax,word ptr es:[4*13h] ;move real mode Int 13h
mov word ptr [bp].lParam,ax ; handler address to
mov ax,word ptr es:[4*13h+2]; lParam on stack frame
mov word ptr [bp].lParam+2,ax
pop es
assume es:DGROUP
mov ah,13h ;wParam1 = int #, function
mov al,byte ptr [bp].intUserAX+1
mov [bp].wParam1,ax
cmp al,02 ;call special read/write routine
jb i13_not_rw ; if this is a read/write sectors
cmp al,03 ; request
ja i13_not_rw
call ReadWriteSectors ;common Int 13h/25h/26h read/write code
jmp short i13_done
i13_not_rw:
SwitchToRealMode ;otherwise, do the service ourself
pop es
pop ds
assume ds:NOTHING,es:NOTHING,ss:DGROUP
popa
sub sp,8 ; make room for stack frame
push bp
mov bp,sp
push es
push ax
xor ax,ax
mov es,ax
mov [bp + 8],cs
mov [bp + 6],word ptr (offset i13_10)
mov ax,es:[13h*4]
mov [bp + 2],ax
mov ax,es:[13h*4 + 2]
mov [bp + 4],ax
pop ax
pop es
pop bp
retf
i13_10: pushf
FCLI
pusha
push ds
push es
mov bp,sp ;restore stack frame pointer
SwitchToProtectedMode
assume ds:DGROUP,es:DGROUP,ss:NOTHING
FSTI ;allow HW interrupts
; Perform fixups on the return register values.
i13_done:
mov ax,[bp].pmUserAX ;get original function code
call IntExit13
FCLI ;LeaveIntHandler requires ints off
call LeaveIntHandler ;restore caller's registers, stack
assume ds:NOTHING,es:NOTHING
riret
PMIntr13 endp
; -------------------------------------------------------
; IntEntry13 -- This routine performs translations and
; buffering of Int 13h requests on entry.
;
assume ds:DGROUP,es:DGROUP,ss:NOTHING
public IntEntry13
IntEntry13 proc near
cmp ah,02 ;Read sectors?
jb @f
cmp ah,03 ;Write sectors?
ja @f
mov [bp].intUserBX,offset DGROUP:rgbXfrBuf1 ;use DOSX buffer
ret
@@:
cmp ah,04h ;Verify sectors?
jnz @f
mov [bp].intUserES,0F000h ;older versions of verify need a buff,
mov [bp].intUserBX,0 ; we just point them at the BIOS!
ret
@@:
cmp ah,05h ;Format track?
jnz @f
mov si,bx ;es:bx -> 512 byte buffer to copy down
mov di,offset DGROUP:rgbXfrBuf1
mov [bp].intUserBX,di
mov ds,[bp].pmUserES
mov cx,256 ;might be good to check segment limit
cld ; on callers source!
rep movsw
push es
pop ds
ret
@@:
ret
IntEntry13 endp
; -------------------------------------------------------
; IntExit13 -- This routine performs translations and
; buffering of Int 13h requests on exit.
;
assume ds:DGROUP,es:DGROUP,ss:NOTHING
public IntExit13
IntExit13 proc near
; Functions 02h (Read sectors) and 03h (Write sectors) return a count of
; sectors transfered in AL. Since we may break the transfer up into a
; number of transfers, we have to return the total # that we transfered,
; not the number of the last bios request.
cmp ah,02h
jb @f
cmp ah,03h
ja @f
mov al,byte ptr cSectorsTransfered
mov byte ptr [bp].intUserAX,al
@@:
; Functions 02h (Read sectors), 03h (Write sectors), 04h (Verify sectors),
; and 05h (Format track) need to have the caller's value of bx restored.
cmp ah,02h ;Read sectors?
jb @f
cmp ah,05 ;Format track?
ja @f
mov ax,[bp].pmUserBX ;restore caller's BX value
mov [bp].intUserBX,ax
ret
@@:
; Functions 08h (Get Drive Parameters) and 18h (Set Drive Type for Format)
; return a pointer in ES:DI. Map the segment in ES to a selector
cmp ah,08h ;Get Drive Parameters
jz i13_map_es
cmp ah,18h
jnz @f
i13_map_es:
test byte ptr [bp].intUserFL,1 ;don't bother to map ES if
jnz @f ; function failed (carry set)
i13_do_it:
mov ax,[bp].intUserES ;returns a pointer in ES:DI, get
mov bx,STD_DATA ; a selector for it
call ParaToLDTSelector
mov [bp].pmUserES,ax
ret
@@:
ret
IntExit13 endp
; -------------------------------------------------------
subttl INT 25h/26h Absolute Disk Read/Write
page
; -------------------------------------------------------
; INT 25h/26h ABSOLUTE DISK READ/WRITE
; -------------------------------------------------------
; PMIntr25 -- This routine provides the protected-to-real
; mode mapping for Int 25h (Absolute Disk Read)
;
; In: al - drive # (0 = A, 1 = B, ...)
; cx - # of sectors to read
; dx - starting sector #
; ds:bx - selector:offset of buffer
;
; -- or --
;
; al - drive #
; cx - -1
; ds:bx - pointer to 5 word parameter block
;
; Out: if successful, carry clear
; if unsuccessful, carry set and
; ax - error code
assume ds:DGROUP,es:DGROUP
public PMIntr25
PMIntr25 proc near
cld ;cya...
call EnterIntHandler ;build an interrupt stack frame
assume ds:DGROUP,es:DGROUP ; also sets up addressability
FSTI ;allow HW interrupts
mov ah,25h
call IntEntry2X ;perform translations/buffering
; Do the read
push es
mov ax,SEL_RMIVT OR STD_RING
mov es,ax
assume es:nothing
mov ax,word ptr es:[4*25h] ;move real mode Int 25h
mov word ptr [bp].lParam,ax ; handler address to
mov ax,word ptr es:[4*25h+2]; lParam on stack frame
mov word ptr [bp].lParam+2,ax
pop es
assume es:DGROUP
mov ah,25h ;wParam1 = int #
mov [bp].wParam1,ax
call ReadWriteSectors ;common Int 13h/25h/26h read/write code
; Perform fixups on the return register values.
mov ah,25h
call IntExit2X ;perform translations/buffering
FCLI
call LeaveIntHandler ;restore caller's registers, stack
assume ds:NOTHING,es:NOTHING
; Int 25 & 26 leave the caller's flags on the stack, but we want to return
; with the flags returned by the real mode ISR (which LeaveIntHandler has
; incorporated into the caller's flags), so make a copy of the flags and
; pop them into the flags register before returning.
push ax
push bp
mov bp,sp ;bp -> BP AX IP CS FL
mov ax,[bp+8]
xchg ax,[bp+2] ;bp -> BP FL IP CS FL
pop bp
npopf
retf
PMIntr25 endp
; -------------------------------------------------------
; PMIntr26 -- This routine provides the protected-to-real
; mode mapping for Int 26h (Absolute Disk Write)
;
; In: al - drive # (0 = A, 1 = B, ...)
; cx - # of sectors to write
; dx - starting sector #
; ds:bx - selector:offset of buffer
;
; -- or --
;
; al - drive #
; cx - -1
; ds:bx - pointer to 5 word parameter block
;
; Out: if successful, carry clear
; if unsuccessful, carry set and
; ax - error code
assume ds:DGROUP,es:DGROUP
public PMIntr26
PMIntr26 proc near
cld ;cya...
call EnterIntHandler ;build an interrupt stack frame
assume ds:DGROUP,es:DGROUP ; also sets up addressability
FSTI ;allow HW interrupts
mov ah,26h
call IntEntry2X ;perform translations/buffering
; Do the write
push es
mov ax,SEL_RMIVT OR STD_RING
mov es,ax
assume es:nothing
mov ax,word ptr es:[4*26h] ;move real mode Int 25h
mov word ptr [bp].lParam,ax ; handler address to
mov ax,word ptr es:[4*26h+2]; lParam on stack frame
mov word ptr [bp].lParam+2,ax
pop es
assume es:DGROUP
mov ah,26h ;wParam1 = int #
mov [bp].wParam1,ax
call ReadWriteSectors ;common Int 13h/25h/26h read/write code
; Perform fixups on the return register values.
mov ah,26h
call IntExit2X ;perform translations/buffering
FCLI
call LeaveIntHandler ;restore caller's registers, stack
assume ds:NOTHING,es:NOTHING
; Int 25 & 26 leave the caller's flags on the stack, but we want to return
; with the flags returned by the real mode ISR (which LeaveIntHandler has
; incorporated into the caller's flags), so make a copy of the flags and
; pop them into the flags register before returning.
push ax
push bp
mov bp,sp ;bp -> BP AX IP CS FL
mov ax,[bp+8]
xchg ax,[bp+2] ;bp -> BP FL IP CS FL
pop bp
npopf
retf
PMIntr26 endp
; -------------------------------------------------------
; IntEntry2X -- This routine performs translations and
; buffering of Int 25h and 26h requests on entry.
;
assume ds:DGROUP,es:DGROUP,ss:NOTHING
public IntEntry2X
IntEntry2X proc near
cmp [bp].intUserCX,-1 ;DOS 4.0 extended read/write?
jnz e2x_dsbx ; no, just go map DS:BX
mov ds,[bp].pmUserDS ; yes, copy down parameter blk
assume ds:NOTHING
mov si,[bp].pmUserBX
mov di,offset rgbXfrBuf0
cld
movsw ;32-bit sector #
movsw
movsw ;# sectors to read/write
mov ax,offset rgbXfrBuf1 ;replace pointer with addr of
stosw ; our own low buffer
IFDEF ROM
GetPMDataSeg
ELSE
mov ax,segDXDataPM ;segment, not selector
ENDIF
stosw
push es
pop ds
assume ds:DGROUP
mov [bp].intUserBX,offset rgbXfrBuf0
ret
e2x_dsbx: ;standard read/write, just redirect DS:BX
mov [bp].intUserBX,offset rgbXfrBuf1
ret
IntEntry2X endp
; -------------------------------------------------------
; IntExit2X -- This routine performs translations and
; buffering of Int 25h and 26h requests on exit.
;
assume ds:DGROUP,es:DGROUP,ss:NOTHING
public IntExit2X
IntExit2X proc near
mov ax,[bp].pmUserBX ;restore caller's BX
mov [bp].intUserBX,ax
ret
IntExit2X endp
; -------------------------------------------------------
subttl Disk Utility Routines
page
; -------------------------------------------------------
; DISK UTILITY ROUTINES
; -------------------------------------------------------
; ReadWriteSectors -- Common code to read/write disk sectors for
; Int 13h/25h/26h.
;
; In: lParam - seg:off of real mode interrupt handler
; wParam1 - int #, and possible subfunction
; regs on stack
assume ds:DGROUP,es:DGROUP,ss:NOTHING
public ReadWriteSectors
ReadWriteSectors proc near
pop [bp].wParam2 ;save return addr higher on stack
; Setup the global data items for the read/write--pointer to caller's
; buffer, # sectors to read/write, and sector size.
cmp byte ptr [bp].wParam1+1,13h ;Int 13h?
jnz rws_dos_size
mov ax,[bp].pmUserBX ;ES:BX points to caller's buf
mov word ptr lpSectorData,ax
mov ax,[bp].pmUserES
mov word ptr [lpSectorData+2],ax
mov al,byte ptr [bp].intUserAX ;# sectors caller wants to
xor ah,ah ; read or write
mov cSectorsToTransfer,ax
mov ah,08h ;get drive parameters
mov dx,[bp].intUserDX ; for drive in DL
push ax
SwitchToRealMode
pop ax
pushf ;have BIOS get the drive data
sub sp,8 ; make room for stack frame
push bp
mov bp,sp
push es
push ax
xor ax,ax
mov es,ax
mov [bp + 8],cs
mov [bp + 6],word ptr (offset rws_10)
mov ax,es:[13h*4]
mov [bp + 2],ax
mov ax,es:[13h*4 + 2]
mov [bp + 4],ax
pop ax
pop es
pop bp
retf
rws_10: jnc @f
mov cx,512 ;according to PS/2 tech ref, some
jmp short rws_to_pm ; old bios versions may fail this,
@@: ; just use 512 in that case
mov cl,es:[di+3] ;sector size shift factor (0,1,2,3)
mov ax,128
shl ax,cl ;ax now = sector size
mov cx,ax
rws_to_pm:
SwitchToProtectedMode
FSTI ;don't need them disabled
if DEBUG ;------------------------------------------------------------
cmp cx,512
jz @f
Debug_Out "Odd sector size = #CX"
@@:
endif ;DEBUG --------------------------------------------------------
jmp short rws_have_size
; Before DOS 4.0, CX was the # sectors to read/write. Starting with 4.0,
; if CX == -1, DS:BX points to a parameter block which contains the
; sector size at offset 4.
rws_dos_size:
mov cx,[bp].intUserCX ;caller's cs == -1?
inc cx
jcxz rws_dos_4
dec cx ; no, then cx has sector count
mov ax,[bp].pmUserBX ; and DS:BX points to buffer
mov word ptr lpSectorData,ax
mov ax,[bp].pmUserDS
mov word ptr [lpSectorData+2],ax
jmp short rws_dos_num_secs
rws_dos_4:
mov cx,word ptr rgbXfrBuf0+4 ; yes, get count from low param block
push ds ; and DS:BX points to param block
mov ds,[bp].pmUserDS ; which contains pointer to buffer
assume ds:NOTHING
mov bx,[bp].pmUserBX
mov ax,word ptr ds:[bx+6]
mov word ptr lpSectorData,ax
mov ax,word ptr ds:[bx+8]
mov word ptr [lpSectorData+2],ax
pop ds
assume DS:DGROUP
rws_dos_num_secs:
mov cSectorsToTransfer,cx ;number sectors to read/write
mov cx,512 ;I've been assured by a WINFILE developer
; that the Int 25/26 sector size will always
; be 512 bytes.
; CX now has the drive's sector size. Determine how many sectors we can
; transfer at a time
rws_have_size:
mov cbSectorSize,cx ;save sector size for later
xor dx,dx
mov ax,CB_XFRBUF1 ;buf size / sector size = sectors per
div cx ; transfer
if DEBUG ;------------------------------------------------------------
or ax,ax
jnz @f
Debug_Out "Sectors per transfer = 0!"
@@:
endif ;DEBUG --------------------------------------------------------
mov cSectorsPerTransfer,ax
xor ax,ax
mov cSectorsTransfered,ax ;sectors transfered so far = 0
mov cSectorsThisTransfer,ax ;sectors transfered last time = 0
; Get/init a selector that we'll use to reference the caller's buffer.
mov ax,word ptr [lpSectorData+2] ;get lma of caller's buffer
call GetSegmentAddress
add dx,word ptr lpSectorData
adc bx,0
call AllocateSelector ;build a sel/dscr pointing
mov cx,0FFFFh
cCall NSetSegmentDscr,<ax,bx,dx,0,cx,STD_DATA>
xor bx,bx
mov word ptr lpSectorData,bx ;use that as the buffer ptr
mov word ptr [lpSectorData+2],ax
; ======================================================================
; Main sector read/write loop ------------------------------------------
; ======================================================================
rws_do_it_loop:
; Calculate how many sectors to transfer this time around, set starting
; sector number based on how many transfered last time.
mov ax,cSectorsToTransfer
sub ax,cSectorsTransfered
jnz @f
jmp rws_done
@@:
cmp ax,cSectorsPerTransfer
jna @f
mov ax,cSectorsPerTransfer
@@:
mov bx,cSectorsThisTransfer ;STIll # R/W from last loop
cmp byte ptr [bp].wParam1+1,13h ;BIOS read/write?
jnz rws_use_dos_size
push [bp].pmUserAX ;the BIOS does not save
pop [bp].intUserAX ; registers across calls to
push [bp].pmUserCX ; it so if we're doing
pop [bp].intUserCX ; multiple calls to buffer
push [bp].pmUserDX ; data, restore the initial
pop [bp].intUserDX ; register values
mov byte ptr [bp].intUserAX,al ;# sectors in AL
add byte ptr [bp].intUserCX,bl ;update new start sector in CL
jmp short rws_size_start_set
rws_use_dos_size:
cmp [bp].intUserCX,0FFFFh ;normal or extended DOS?
jz rws_dos4_size
mov [bp].intUserCX,ax ; normal, # sectors in CX
add [bp].intUserDX,bx ; new start sector in DX
jmp short rws_size_start_set
rws_dos4_size:
mov word ptr rgbXfrBuf0+4,ax ; extended, # sectors & 32 bit
add word ptr rgbXfrBuf0,bx ; start sector in parameter
adc word ptr rgbXfrBuf0+2,0 ; block
rws_size_start_set:
; At this point, AX has the number of sectors to transfer. If this is a
; write, copy a buffer of data from the caller's buffer.
mov cSectorsThisTransfer,ax ;in case it's a read
cmp [bp].wParam1,1303h ;BIOS write?
jz rws_buf_write
cmp byte ptr [bp].wParam1+1,26h ;DOS write?
jnz rws_not_write
rws_buf_write:
mul cbSectorSize ;AX now = # bytes to transfer
mov cx,ax ;can safely assume < 64k
shr cx,1 ;# words to move
lds si,lpSectorData
assume ds:NOTHING
mov di,offset rgbXfrBuf1
cld
rep movsw
push es
pop ds
assume ds:DGROUP
mov word ptr lpSectorData,si ;update src ptr for next time
call NormalizeBufPtr ; and normalize it
rws_not_write:
; Switch to real mode, do the transfer.
SwitchToRealMode
assume ds:DGROUP,es:DGROUP
push word ptr [bp].lParam
pop word ptr lpRmISR
push word ptr [bp].lParam+2
pop word ptr lpRmISR+2
cmp byte ptr [bp].wParam1+1,13h
jnz rws_call_dos
pop es
pop ds
assume ds:NOTHING,es:NOTHING,ss:DGROUP
popa
call lpRmISR
pushf
FCLI
jmp short rws_save_regs
rws_call_dos:
pop es
pop ds
assume ds:NOTHING,es:NOTHING,ss:DGROUP
popa
call lpRmISR
pop word ptr lpRmISR ;int 25/26 leave flags on stack,
pushf ; pop them to nowhere
FCLI
rws_save_regs:
pusha
push ds
push es
mov bp,sp ;restore stack frame pointer
SwitchToProtectedMode
assume ds:DGROUP,es:DGROUP,ss:NOTHING
FSTI ;allow HW interrupts
; If the call failed, then cut out now without further processing...
test byte ptr [bp].intUserFL,1 ;CY set?
jnz rws_done
; If this was a successful read, copy the data back to the caller.
cmp [bp].wParam1,1302h ;BIOS read?
jz rws_buf_read
cmp byte ptr [bp].wParam1+1,25h ;DOS read?
jnz rws_not_read
rws_buf_read:
mov ax,cSectorsThisTransfer ;calc size of data to move
mul cbSectorSize
mov cx,ax
shr cx,1 ;in words
les di,lpSectorData ;caller's buffer pointer
assume es:NOTHING
mov si,offset rgbXfrBuf1
cld
rep movsw
push ds
pop es
assume es:DGROUP
mov word ptr lpSectorData,di ;update dest ptr for next time
call NormalizeBufPtr ; and normailize it
rws_not_read:
mov ax,cSectorsThisTransfer ;count total sectors transfered
add cSectorsTransfered,ax
jmp rws_do_it_loop ;go do another buffer full
rws_done:
mov ax,word ptr [lpSectorData+2] ;release our temp buffer sel
call FreeSelector
jmp [bp].wParam2
ReadWriteSectors endp
; -------------------------------------------------------
; This routine 'normalizes' the far pointer in lpSectorData such that
; the selector/descriptor points to where the selector:offset currently
; points
assume ds:DGROUP,es:NOTHING
NormalizeBufPtr proc near
mov ax,word ptr [lpSectorData+2] ;get segment base address
call GetSegmentAddress
add dx,word ptr lpSectorData ;add in current offset
adc bx,0
call SetSegmentAddress ;make that the new seg base
xor bx,bx
mov word ptr lpSectorData,bx ; with a zero offset
ret
NormalizeBufPtr endp
DXPMCODE ends
;****************************************************************
end