;;,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,
;;                                                                            ;
;;    ________________                                                        ;
;;   |                |  32-bit movmem() replacement, real or protected mode  ;
;;   |  MOVMEM.ASM    |  (plus varieties for assembly-language use and for    ;
;;   |________________|  applications that require byte-at-a-time moves)      ;
;;                                                                            ;
;;                                                                            ;
;;   Copyright (c) 1995-1997 by Galacticomm, Inc.  All rights reserved.       ;
;;                                                                            ;
;;                                                                            ;
;;   (derived from GALMOV.ASM, n.b.)                      RNStein Jul 1995    ;
;;   movmemb() and movmemb_reg versions, real-mode supt.  RNStein Mar 1996    ;
;;                                                                            ;
;;.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,;

ifdef LARGEMAC
; large model
        include modelmtl.mac
else
; huge model
        include modelhpl.mac
endif
        include jumpfar.mac

        public  galmovmem_reg
        public  galmovmemb_reg
        extrn   _cputype:far

        pseg    galmovmem
        P386N

        proc_def    galmovmem   ; MOVMEM(), C-callable version
                                ; (4-byte moves if possible)
                                ;
                                ; void
                                ; galmovmem(
                                ; void *src,
                                ; void *dst,
                                ; unsigned int nbytes);
                                ;
                                ; Works in protected mode or real mode.
        push    bp
        mov     bp,sp
        push    ds              ; \
        push    si              ;  > C-friendly register saving
        push    di              ; /
        lds     si,[bp+6]       ; \
        les     di,[bp+10]      ;  > set up register parameters
        mov     cx,[bp+14]      ; /
        call    far ptr galmovmem_reg ; call register-version galmovmem
        pop     di              ; \
        pop     si              ;  > C-friendly register restoration
        pop     ds              ; /
        pop     bp
        ret
        end_proc


        proc_def    galmovmemb  ; MOVMEMB(), C-callable version (byte moves only)
                                ; Helps when moving *from* screen memory for
                                ; some video drivers.  (galmovmem() is OK
                                ; moving *to* such memory because the destination
                                ; is always word-aligned and dword-aligned.)
                                ;
                                ; void
                                ; galmovmemb(
                                ; void *src,
                                ; void *dst,
                                ; unsigned int nbytes);
                                ;
                                ; Works in protected mode or real mode.
        push    bp
        mov     bp,sp
        push    ds              ; \
        push    si              ;  > C-friendly register saving
        push    di              ; /
        lds     si,[bp+6]       ; \
        les     di,[bp+10]      ;  > set up register parameters
        mov     cx,[bp+14]      ; /
        call    far ptr galmovmemb_reg ; call register-version galmovmemb
        pop     di              ; \
        pop     si              ;  > C-friendly register restoration
        pop     ds              ; /
        pop     bp
        ret
        end_proc


galmovmem_reg   proc    far     ; GALMOVMEM_REG, Assembly-callable version
                                ; (4-byte moves if possible)
                                ; (real-mode version will jumps GALMOVMEMB_REG
                                ; if 286 or older processor detected)
                                ;
                                ; move CX bytes from DS:SI to ES:DI
                                ; only segment registers are preserved
                                ;
                                ; Works in protected mode or real mode.
        cmp     cx,8            ; will there definitely be >= 1 4-byte chunk?
        jfl     galmovmemb_reg  ; uncertain less than 8 bytes, byte-at-a-time
        ifdef   PHARLAP         ; Phar Lap pointer comparison:
        mov     ax,es
        mov     bx,ds
        cmp     ax,bx           ;     same selector?
        jne     fwdmov          ;     no, forward move is assumed safe
        cmp     si,di           ;     yes, source offset higher?
        ja      fwdmov          ;     yes, shift low-ward, fwd move desirable
        jmp     revmov          ;     no, shift high-ward, rev move desirable
        else                    ; Real-mode CPU check and pointer comparison:
        push    ds              ;     preserving registers of interest
        push    si
        push    es
        push    di
        push    cx
        pushf
        call    _cputype        ;     check CPU type
        popf
        pop     cx
        pop     di
        pop     es
        pop     si
        pop     ds
        cmp     ax,386          ;     less than a 386?
        jfl     galmovmemb_reg  ;     yes, move a byte at a time
        mov     ax,ds           ;     no, will attempt 4-byte-at-a-time move
        mov     bx,si
        shr     bx,4
        add     ax,bx           ;     AX = normalized DS (from DS:SI)
        push    ax
        mov     ax,es
        mov     bx,di
        shr     bx,4
        add     bx,ax           ;     BX = normalized ES (from ES:DI)
        pop     ax
        cmp     ax,bx           ;     compare normalized segments...
        ja      fwdmov          ;     source >  dest (seg):  forward move
        jb      revmov          ;     source <  dest (seg):  reverse move
                                ;     source =  dest (seg)...
        mov     ax,si
        and     ax,0FH
        mov     bx,di
        and     bx,0FH
        cmp     ax,bx           ;     compare normalized offsets...
        ja      fwdmov          ;     source >  dest (off):  forward move
        jmp     revmov          ;     source <= dest (off):  reverse move
        endif

fwdmov:                         ; FORWARD MOVE
        cld
        xor     ax,ax           ; no, seek dword alignment...
        sub     ax,di
        and     ax,3            ; AX=bytes in preceding dword fragment
        jz      fmpre
        push    cx
        mov     cx,ax
    rep movsb                   ; move preceding dword frag one byte at a time
        pop     cx
fmpre:
        sub     cx,ax           ; adjust byte-count
        mov     dx,cx
        and     dx,3            ; DX=bytes in aftermath dword fragment
        shr     cx,2            ; CX=dword-count
    rep movsd                   ; smoke em
        mov     cx,dx
        test    cx,cx
        jz      fmpost
    rep movsb                   ; move bytes one at a time
fmpost:
        jmp     donmov

revmov:                         ; REVERSE MOVE
        std
        add     si,cx
        add     di,cx
        mov     ax,di
        dec     si              ; SI points to last source byte
        dec     di              ; DI points to last destination byte
                                ; no, seek dword alignment...
        and     ax,3            ; AX=bytes in high-address (early) fragment
        jz      rmpre
        push    cx
        mov     cx,ax
    rep movsb                   ; move high-address dword frag 1 byte at a time
        pop     cx
rmpre:
        sub     cx,ax           ; adjust byte-count
        sub     di,3            ; scoot destination to dword boundary
        sub     si,3            ; source tags along (alignment unknown)
        mov     dx,cx
        and     dx,3            ; DX=bytes in low-address (late) fragment
        shr     cx,2            ; CX=dword-count
    rep movsd                   ; smoke em, in reverse
        add     di,3            ; scoot back to next destination byte (if any)
        add     si,3            ; source tags along
        mov     cx,dx
        test    cx,cx
        jz      rmpost
    rep movsb                   ; move bytes one at a time
rmpost:
        cld
donmov:
        ret
galmovmem_reg   endp



galmovmemb_reg   proc    far    ; GALMOVMEMB_REG, Assembly-callable version
                                ; (byte-moves only)
                                ; (free of special 386-only tricks)
                                ;
                                ; move CX bytes from DS:SI to ES:DI
                                ; only segment registers are preserved
                                ;
                                ; Works in protected mode or real mode.
        test    cx,cx
        jz      donmovb         ; skip entire routine if zero bytes
        ifdef   PHARLAP         ; Phar Lap pointer comparison:
        mov     ax,es
        mov     bx,ds
        cmp     ax,bx           ;     same selector?
        jne     fwdmovb         ;     no, forward move is assumed safe
        cmp     si,di           ;     yes, source offset higher?
        ja      fwdmovb         ;     yes, shift low-ward, fwd move desirable
        jmp     revmovb         ;     no, shift high-ward, rev move desirable
        else                    ; Real-mode pointer comparison:
        mov     ax,ds
        mov     bx,si
        shr     bx,1
        shr     bx,1
        shr     bx,1
        shr     bx,1
        add     ax,bx           ;     AX = normalized DS (from DS:SI)
        push    ax
        mov     ax,es
        mov     bx,di
        shr     bx,1
        shr     bx,1
        shr     bx,1
        shr     bx,1
        add     bx,ax           ;     BX = normalized ES (from ES:DI)
        pop     ax
        cmp     ax,bx           ;     compare normalized segments...
        ja      fwdmovb         ;     source >  dest (seg):  forward move
        jb      revmovb         ;     source <  dest (seg):  reverse move
                                ;     source =  dest (seg)...
        mov     ax,si
        and     ax,0FH
        mov     bx,di
        and     bx,0FH
        cmp     ax,bx           ;     compare normalized offsets...
        ja      fwdmovb         ;     source >  dest (off):  forward move
        jmp     revmovb         ;     source <= dest (off):  reverse move
        endif

fwdmovb:                        ; FORWARD MOVE
        cld
    rep movsb                   ; move bytes one at a time
        jmp     donmovb

revmovb:                        ; REVERSE MOVE
        std
        add     si,cx
        add     di,cx
        dec     si              ; SI points to last source byte
        dec     di              ; DI points to last destination byte
    rep movsb                   ; move bytes one at a time
        cld
donmovb:
        ret
galmovmemb_reg  endp

        endps
;
; Note the ever-important "jfl" far-jump macros in galmovmem_reg.  These are
; not required to avoid assembly errors (the assembler thinks we're coding
; for a 386).  But they are required to work on a 286!
;
; Note:  Assembly-callable galmovmem_reg, and C-callable galmovmem(), have
; a slight vulnerability in protected mode, as does the Borland/Phar
; Lap library movmem().  There could only be a problem for memory that had
; multiple selector aliases AND only when a call to movmem() could use one
; alias for source and a different for destination AND the two regions overlap
; in physical memory AND the destination has a *higher* physical address than
; the source.  By the way, the aliases of high-scrutiny memory checking does
; not create a vulnerability like this, because all code outside GALMEMDB.C
; uses only the alias selector.
;
; A thorough correction would be to convert source and destination pointers to
; absolute physical addresses before doing the overlap check.  But a Phar Lap
; call would be required and experience indicates this can come with a
; significant performance penalty.
;
; Real mode pointers are compared in normalized form, but the unnormalized
; forms are used in performing the actual move.
;
; Both protected-mode and real-mode versions of galmovmem() and galmovmem_reg
; put the *destination* on a 4-byte (32-bit) boundary.  For applications where
; the source reads must not straddle 4-byte or 2-byte boundaries (e.g. some
; wanky ATI video card or something), use galmovmemb() or galmovmemb_reg.

        end

