;       SCCSID = @(#)codelock.asm       6.2 91/10/17
SCCSID  equ     "@(#)codelock.asm       6.2 91/10/17"
;
;       CODELOCK.ASM -- Code locking procedures
;
;       This file contains code for locking and unlocking ranges of
;       swappable kernel code, and for initializing the code lock info.
;
;       A code range is a pre-determined group of routines that perform
;       some related task, such as named pipe code or oplock code.
;       These code ranges are in turn grouped into swappable segments.
;       A lock group can consist of a swappable segment in its entirety
;       or of a code range.
;
;       At init time, we need to take the list of lock range start and
;       end addresses and process them, placing the results into structures
;       that will be used at run time for the info needed by VMLockMem.
;
;       We keep track of the number of threads currently using a lock range.
;       VMLockMem is called only when the count goes from 0 to 1;  unlock is
;       called only when the count goes from 1 to 0.  Seamphores guard
;       the count and VMLockMem/UnLock calls.
;
;       To lock a range of code, macros (see codelock.inc) are placed at
;       the the beginning and end of each routine that is an entry point
;       into the range (ie, any routine that is called from outside the
;       range).
;
;       NOTE: See CodeLock.inc for caveats on using the codelock mechanism
;               from other than 16-bit asm code.
;
;       ENTRY POINTS:
;
;       CodeLockProc
;       CodeUnlockProc
;       InitCodeLocks
;
;       MODIFICATION HISTORY:
;               2/7/1990        Ted Miller
;               4/1/1996        DR Brown    150371 - System Anchor Block


include fnofs.inc
include dosseg.inc
include basemaca.inc
include osmaca.inc
include error.inc
include mi.inc
include osconst.inc
include tkexport.inc
include pgexport.inc
include vmexport.inc
include tom.inc
include codelock.inc
include kmode.inc

        ModName codelock,FNO_CODELOCK_ASM

        EXTRN           _TKSleep:NEAR
        EXTRN           _VMLockMem:NEAR
        EXTRN           _VMUnlock:NEAR

        EXTRN32         _TOMSet
        EXTRN32         _TOMCancel
        EXTRN32         KMALLOCHOOK
        EXTRN32         _KSEMInit
        EXTRN32         _KSEMRequestMutex
        EXTRN32         _KSEMReleaseMutex
        EXTRN32         _CodeLockHook

        ReferFlat       endofnampipe1
        ReferFlat       startofnampipe2
        ReferFlat       _phookCodeLock
        ReferFlat       _pTCBCur,dword



DATA segment

ifdef FSSTRICT
        PUBLIC  _fFSSwapStarted, fFSSwapStarted
_fFSSwapStarted label   BYTE            ; D150371
fFSSwapStarted  db      0               ; set to -1 after the high4code is made
                                        ; swappable by _LDRFreeInit
endif

public  _CodeLockArray
_CodeLockArray  DB (NUMCODELOCKGROUPS * size CodeLock_s) dup (?)

DATA ends

;NOTE: the CodeLockBlock_s and CodeLockBlock macros MUST match.

CodeLockBlock_s struc
clbs_end        dd      ?
clbs_start      dd      ?
CodeLockBlock_s ends

;
; InitTime structure.  Creates start/end address pair for a lock range.
; Processed at init time to form CodeLock structs.
;
CodeLockBlock   macro   start,end

                ReferFlat start,BYTE
                ReferFlat end,BYTE

                INITDATA SEGMENT
                .386p
                dd      offset FLAT:&end
                dd      offset FLAT:&start
                CpuMode reset
                INITDATA ENDS
                endm


INITDATA        SEGMENT
;
; Define the start and end addresses of each lock range here.
;
; BUGBUG tedm (2/5/90):
; It seems that there is a masm bug that prevents it from generating a 32-bit
; fixup-record for an external at offset 0 in a segment.
;
dw      0                               ; because of masm bug
        PUBLIC CodeLockInitInfo
CodeLockInitInfo        LABEL   BYTE

INITDATA        ENDS

.errnz          FATEA_GROUP     - (0* size CodeLock_s)
.errnz          NMPIPE_GROUP    - (1* size CodeLock_s)
.errnz          FSCTL_GROUP     - (2* size CodeLock_s)
.errnz          FILEIO_GROUP    - (3* size CodeLock_s)
.errnz          OPLOCK_GROUP    - (4* size CodeLock_s)
.errnz          DRVLOCK_GROUP   - (5* size CodeLock_s)
.errnz          NEWNODE_GROUP   - (6* size CodeLock_s)
.errnz          FSSWAP_GROUP    - (7* size CodeLock_s)
.errnz          NOTIFY_GROUP    - (8* size CodeLock_s)

CodeLockBlock   _StartFATEASWAPCODE,_EndFATEASWAPCODE
CodeLockBlock   _StartNMPIPEGROUP,_EndNMPIPEGROUP
CodeLockBlock   _StartFSCTLGROUP,_EndFSCTLGROUP
CodeLockBlock   _StartFILEIOGROUP,_EndFILEIOGROUP
CodeLockBlock   _StartOPLOCKGROUP,_EndOPLOCKGROUP
CodeLockBlock   _StartDRVLOCKGROUP,_EndDRVLOCKGROUP
CodeLockBlock   _StartNODEGROUP,_EndNODEGROUP
CodeLockBlock   _StartFSSWAPCODE,_EndFSSWAPCODE
CodeLockBlock   _StartNOTIFYGROUP,_EndNOTIFYGROUP

CpuMode 386

        FARCODE HIGH32
CINIT_TEXT SEGMENT

;***    InitCodeLocks - initialize code lock information for swappable FS code
;
;       This routine is responsible for setting up the runtime code lock
;       structures with the information needed by code locking/unlocking
;       procedures for calling _VMLockMem/unlock.  What we have on entry is
;       a table of start/end pairs for each range of code that can be locked.
;       This routine transforms that information into a start address/range-
;       length pair which is then stored in a slot in an array of structures.
;
;       This routine also checks to see whether nampipe1.asm and nampipe2.asm
;       have been linked adjacently (nampipe1 first).
;
;       ENTRY: ds,es FLAT
;
;       EXIT:  CodeLockArray is set up
;
;       USES:  ALL
;
;       PSEUDOCODE:
;
;       for(grp=1 to NUMCODELOCKGROUPS) {
;           CodeLockArray[grp].startaddr = CodeLockInitInfo[grp].start;
;           CodeLockArray[grp].rangelen  = CodeLockInitInfo[grp].end -
;                                            CodeLockInitInfo[grp].start + 1;
;           KSEMInit(&CodeLockArray[grp].semaphore,KRNL_MUTEXSEM);
;       }
;       delta = &startnampipe2 - &endnampipe1;
;       if (delta != 1)
;           Panic();
;

Procedure InitCodeLocks,far32
        ASSUME  DS:NOTHING,ES:NOTHING,FS:NOTHING,GS:NOTHING,SS:TASKAREA

        mov     edi,offset FLAT:_CodeLockArray
        mov     esi,offset FLAT:CodeLockInitInfo

        mov     ecx,NUMCODELOCKGROUPS
        cld

        .errnz  clbs_end
        .errnz  (clbs_start - 4)
        .errnz  (size CodeLockBlock_s - 8)
icl30:
        lodsd                           ; eax -> range end
        sub     eax,[esi]
        inc     eax                     ; eax = range length
        mov     [edi].cl_rangelen,eax   ; save in lock array
        lodsd                           ; eax -> range start
        mov     [edi].cl_startaddr,eax  ; save in lock array
        mov     [edi].cl_count,0        ; initialize count

        lea     edx,[edi].cl_semaphore  ; edx -> semaphore for lock range
        push    ecx
        CCALL32 _KSEMInit,<edx,KRNL_MUTEXSEM,KSEM_NOINTERRUPT>

        .errnz  cl_toe                  ; set and clear timer to initialize it
        CCALL32 _TOMSet,<edi,TOM_WAIT_FOREVER,TOM_W_CODEUNLOCK>
        CCALL32 _TOMCancel,<edi>        ; timer is initialized

        pop     ecx
        add     edi,size CodeLock_s     ; point to next code lock structure
                                        ; esi has automatically been advanced
                                        ; by lodsd's above
        loop    short icl30             ; do all lock ranges

;       Allocate the context hook used for code unlocking
;       KMAllocHook(&CADHandler,0,0,0,KM_GLOBAL_HOOK,&CADHookHandle)

        mov     esi,offset FLAT:_phookCodeLock; pointer to hook handle
        mov     edi,offset FLAT:_CodeLockHook ; routine to handle CAD
        xor     ecx,ecx
        FlatContext <ds,es>,dx,noassume
        PCALL32 KMALLOCHOOK,<edi,ecx,ecx,ecx,dword ptr KM_GLOBAL_HOOK,esi>
        or      eax,eax
        jnz     short iclie

IFDEF FSSTRICT
        mov     eax,offset FLAT:startofnampipe2
        sub     eax,offset FLAT:endofnampipe1
        test    ax,0fffch               ; for DWORD alignment
        jz      short iclx
        Panic   <'Nampipe1 and Nampipe2 not linked adjacently'>
iclx:
ENDIF
        ret

iclie:  Panic   <'InitCodeLocks: context hook not allocated'>


EndProc InitCodeLocks

CINIT_TEXT ENDS


        FARCODE HIGH32
HIGH32CODE SEGMENT


;***    CodeLockProc - lock a range of code into memory
;
;       This routine calls _VMLockMem on the range associated with the given
;       lock group, if the given range is not already locked.  A counter is
;       used to keep track of how many times the lock group has been entered.
;
;       NOTE:   This routine is designed to be called from the CodeLock macro.
;               Do not call it directly.
;
;       ENTRY:  ebx = lock group
;
;       EXIT:   NC: code group is locked if it is not already locked;
;                   lock group counter incremented
;               CY: ERROR (from VMLockMem), eax = error code
;
;       USES:   ebx, flags (also eax in error case)
;
;       PSEUDOCODE:
;
;       pLockInfo = lockgroup + pCodeLockArray; //address lock info for group
;       if((pLockInfo->count) == 0) {           //range not already locked
;           KSEMRequestMutex(&pLockInfo->semaphore);
;           if ((pLockInfo->flags & ISLOCKED)) {
;              if ((pLockInfo->flags & TOESET)) {
;                  TomCancel(&(pLockInfo->toe))
;              pLockInfo->flags &= ~(UNLOCKPENDING | TOESET)
;           } else {
;              start = pLockInfo->startaddr;
;              size  = pLockInfo->rangelen;
;              Lockhand = &(pLockInfo->lockhandle);
;              while((VMrc=VMLockMem(start,size,PL_LONG,Lockhand)==ERROR_INTERRUPT);
;              pLockInfo->flags |= ISLOCKED
;           }
;           pLockInfo->count++;
;           KSEMReleaseMutex(&pLockInfo->semaphore);
;       }
;       else
;           pLockInfo->count++;
;       if (VMrc)
;           return(CY, eax=error code);
;       else
;           return(NC)
;
Procedure CodeLockProc,<near,far32>
        ASSUME DS:NOTHING,ES:NOTHING,FS:NOTHING,GS:NOTHING,SS:TASKAREA

        SaveReg <ds,es,esi,edx,ecx,eax> ; EAX MUST BE LAST (see below)
        FlatContext <ds,es>,edx

IFDEF FSSTRICT
        mov     edx,[_pTCBCur]
        mov     al,[fFSSwapStarted]
        and     al,[edx].TCBfSwapping   ; Is the current thread swapping?
        jz      short clp05                     ; no, everthing is OK.

clpie:  Panic   <'CodeLockProc: CodeLocking for a swapper thread'>
clp05:
ENDIF


;
; The 'lock group' is actually an offset into the code lock array.  So we
; address the entry for a lock group by adding the base offset of the
; code lock array to the lock group.
;
        add     ebx,offset FLAT:_CodeLockArray  ; ebx -> CodeLockArray entry

        cmp     [ebx].cl_count,0
        je      short clp10

; code is already locked down by some other thread; just increment the lock
; count and get out.

        inc     [ebx].cl_count
        jmp     clp90
;
; Grab the semaphore for this lock group.
;
clp10:  lea     edx,[ebx].cl_semaphore
        CCALL32 _KSEMRequestMutex,<edx,-1>

        test    [ebx].cl_flags,CL_ISLOCKED
        jnz     short clp50             ; code already locked down by
                                        ; somebody else
;
; Now retrieve the start address for the lock group, and the length of
; the range to lock.
;
        mov     esi,CODELOCK_ATTEMPT_COUNT

clp20:  mov     ecx,[ebx].cl_startaddr
        mov     edx,[ebx].cl_rangelen
;
; We're assuming that the lock handle is the first field in the CodeLock
; structure.  Then ebx is the flat pointer to the lock handle.
;
        lea     eax,[ebx].cl_lhand
        CCall   _VMLockMem,<ecx, edx, VML_LONG, eax, VM_NO_ADDR, VM_NO_ADDR>
        or      eax,eax                                 ; clear carry
        jz      short clp40
        cmp     eax,ERROR_INTERRUPT
        je      short clp20
; VMLockMem returned an error other than ERROR_INTERRUPT; return to caller
;  with error
; NOTE: TOS = caller's EAX.
; Replace with error code for later RestoreReg.  Return with carry set.
;

        dec     esi                     ; Was this the last attempt?
        jz      short clp30             ; yes, go return error

        lea     eax,[ebx].CODELOCK_BLOCK_ID     ; create a block ID
        CCall   _TKSleep,<ebx,CODELOCK_RETRY_WAIT,0,0>
        jmp     short clp20             ; go try again

clp30:  stc
        mov     ss:[esp],eax
        jmp     short clp80

clp40:  or      [ebx].cl_flags,CL_ISLOCKED  ; mark as locked, clear carry

IFDEF FSSTRICT                          ; unlock mechanism should not be active
        test    [ebx].cl_flags,(CL_UNLOCKPENDING OR CL_TOESET)
        jz      short clp45             ; unlock mechanism is dormant

clpie2: Panic   <'CodeLockProc: Just locked code with pending unlock'>
clp45:
ENDIF
        jmp     short clp70

clp50:  test    [ebx].cl_flags,CL_TOESET; Is the timer running?
        jz      short clp60             ; no, timer is not running

        .errnz  cl_toe
        CCall   _TOMCancel,<ebx>        ; cancel the timer
clp60:  and     [ebx].cl_flags,NOT (CL_UNLOCKPENDING OR CL_TOESET)

clp70:  inc     [ebx].cl_count                          ;

clp80:  pushfd                                          ; save carry status
        lea     edx,[ebx].cl_semaphore
        CCALL32 _KSEMReleaseMutex,<edx>

        popfd                                           ; restore carry status
clp90:  RestoreReg <eax,ecx,edx,esi,es,ds>
        ret

EndProc CodeLockProc


;***    CodeUnlockProc - unlock a range of code from memory
;
;       This routine calls _VMUnlock on a range of code, if there are no
;       more users of the range.
;
;       NOTE:   This routine is designed to be called from the CodeUnLock
;               macro.  Do not call it directly.
;
;       ENTRY:  ebx = lock group
;
;       EXIT:  code group is unlocked if this is the last process using it
;
;       USES:  ebx, flags
;
;       PSEUDOCODE:
;
;       pLockInfo = lockgroup + pCodeLockArray; //address lock info for group
;       pLockInfo->count --;
;       if((pLockInfo->count == 0)) {           //range needs to be unlocked
;           KSEMRequestMutex(&pLockInfo->semaphore);
;           if(pLockInfo->count == 0) { //we are a last thread out
;               if(pLockInfo->flags & ISLOCKED)
;                  while((rc=VMUnlock(&pLockInfo->lockhandle)==ERROR_INTERRUPT) ;
;                  if(VMSTRICT && rc) Panic;
;               }
;           }
;           KSEMReleaseMutex(&pLockInfo->semaphore);
;       }
;
Procedure CodeUnlockProc,<near,far32>

        ASSUME DS:NOTHING,ES:NOTHING,FS:NOTHING,GS:NOTHING,SS:TASKAREA

        pushfd
        SaveReg <ds,es,eax,ecx,edx>
        FlatContext <ds,es>,edx
;
; The 'lock group' is actually an offset into the code lock array.  So we
; address trhe entry for a lock group by adding the base offset of the
; code lock array to the lock group.
;
        add     ebx,offset FLAT:_CodeLockArray  ; ebx -> CodeLockArray entry

        dec     [ebx].cl_count
        jnz     short cup40
;
; Grab the semaphore for this lock group.
;
        lea     edx,[ebx].cl_semaphore
        CCALL32 _KSEMRequestMutex,<edx,-1>

        cmp     [ebx].cl_count,0        ; Are we still the last one out?
        jnz     short cup35             ; no, another thread has entered

        test    [ebx].cl_flags,CL_ISLOCKED  ; Is the code locked?
        jz      short cup35             ; no, no point in setting the timer

;
; EBX contains the flat pointer to the lock handle structure.  We are assuming
; that the lock handle structure is the first field of the CodeLock structure.
;

        test    [ebx].cl_flags,CL_TOESET; Is the timer running?
        jz      short cup20             ; no, timer is not running

        .errnz  cl_toe
        CCall   _TOMCancel,<ebx>        ; cancel the timer

cup20:  and     [ebx].cl_flags,NOT (CL_UNLOCKPENDING)   ; clear pending unlock
        or      [ebx].cl_flags,CL_TOESET; timer is set
        .errnz  cl_toe
        CCall   _TOMSet,<ebx,CODEUNLOCKDELAY,TOM_W_CODEUNLOCK>

cup35:  lea     edx,[ebx].cl_semaphore
        CCALL32 _KSEMReleaseMutex,<edx>

cup40:  RestoreReg <edx,ecx,eax,es,ds>
        popfd
        ret

EndProc CodeUnlockProc

HIGH32CODE ENDS

        end
