;       SCCSID = @(#)profile.asm        6.1 90/11/17
SCCSID  equ     "@(#)profile.asm        13.20 90/09/27"
PAGE    ,132
TITLE   PROFILE.ASM - Interrupt Manager System Profiling
NAME    PROFILE

;
;*  Title:
;*      PROFILE - System Profiling
;*
;*  Author:
;*      Benjamin W. Slivka
;*      Mark J. Zbikowski
;*      (c) Microsoft Corporation
;*      1985,1986,1987
;*
;*  Description:
;*      Interrupt- and Task-Time routines to support System Profiling.
;*
;*  Modification History:
;*       1-May-1987 BWS Rewrote code to support profiling system AND user code.
;*       7-May-1987 BWS Support simultaneous system and user profiling.
;*      16-May-1987 BWS Count idle time correctly.
;*      09-Jun-1987 BWS Fix calls to SuBuff,SuByte,SuWord to reflect segment
;*                      register trashing.  Updated High Level Design.
;*      16-Sep-1987 MWH Use IRQ0 timer, profile DD
;*      24-Oct-1987 PWU Add AddProcLDTs and pcb_fDosProfile.
;*      18-Feb-1988 BWS Fix bx trashing in LDTSelToName; Fix ProfOn to
;*                      set pcb_flags correctly.
;*      24-Mar-1988 MTS Add PF_KERNEL flag in conjunction with PF_CALLDD to
;*                      allow choice of kernel-support or device driver
;*                      profiling.
;*                      Add PF_VERBOSE flag to allow the option of detail
;*                      kernel profiling.
;*                      Move fProfile flag into pcb_flags (PF_PROFON).
;*                      Fix ProfFree to call ProfOff in case profiling is still
;*                      on.
;*
;*  High Level Design:
;*      A "profile control block" (PCB) contains data for profiling either
;*      a process, or the system as a whole.  The PCB contains the following
;*      data:
;*
;*                        / word    N; number of selectors
;*                       |  word    K; number of kernel selectors
;*              fixed    |  dword   count of idle tics
;*              ---------+  dword   count of other GDT tics
;*              fields   |  dword   count of other LDT tics
;*                       |  dword   count of kernel tics
;*                       |  word    offset to the start of non-kernel selectors
;*                        \ word    record PF_VERBOSE:1, PF_KERNEL:1,
;*                                         PF_CALLDD:1, PF_USER:1
;*
;*                        / word    kernel code selector    0
;*                       |  word    kernel code selector    1
;*                       |      ...
;*                       |  word    kernel code selector  K-1
;*                       |  word    code selector         K
;*                       |  word    code selector         K+1
;*              variable |      ...
;*              ---------+  word    code selector         N-1
;*              fields   |  word    data selector           0
;*                       |  word    data selector           1
;*                       |  word    data selector           2
;*                       |      ...
;*                        \ word    data selector         N-1
;*
;*
;*      Each process may be profiled independently.  TASKAREA:sPCB contains
;*      the selector for the process PCB.
;*
;*      The system as a whole may be profiled.  DOSGROUP:sSysPCB contains
;*      the selector for the system PCB.
;*
;*      DoProfile is called every IRQ 0 (8253 timer) interrupt.  The
;*      timer interrupt rate is set at 512Hz.
;*
;*
;*      DoProfile accumulates data for the Process if sPCB is non-zero AND
;*      the PF_PROFON flag is ON.
;*
;*      DoProfile accumulates data for the System if sSysPCB is non-zero AND
;*      the PF_PROFON flag is ON.
;*
;*      When DoProfile is called (on a periodic timer interrupt), the CS:IP
;*      on the stack is used to collect data.  The CS is used to scan the
;*      list of code selectors.  If a match is found, the corresponding data
;*      selector is used to access a profile data segment.  We zero the two
;*      low-order bits of the stack IP are zeroed, and the result is used as
;*      an index to a dword counter, which is incremented.  If the CS does not
;*      exist in the table, then we increment one of the following dword
;*      counters in the PCB:
;*
;*          pcb_idleTics
;*              count of IDLE time (no task running)
;*          pcb_gdtTics
;*              count in GDT selectors not in selector list
;*          pcb_ldtTics.
;*              count in LDT selectors not in selector list
;*
;*  BUGBUG  13-Feb-1988 bws Do not touch user data directly
;*      1)  Reading module list directly in ProfAlloc!  Should use FuBuff!

;* Notes: This code was changed to add minimal use32 support
;* It should be changed to check that user supplied buffer is
;* legal and to yield during memory copy operations.
;* Profiling DD support has been broken by these changes
;*
;* Suggestions:
;*      1) make doProfile and associated routines use32
;*         and use flat swappable data object to store counts
;*      2) offer a DD help routine to allow timer to give
;*         profiler address of a SetTimer routine to change
;*         timer and then profiler should not eat any interrupts
;*         in int.asm.



PAGE

IFDEF PROFILE

.xlist
include dosseg.inc
include mi.inc
include basemaca.inc
include osmaca.inc
include fnodev.inc
include osconst.inc
include error.inc
include error2.inc
include vmowner.inc
include protmode.inc
include seldesc.inc
include selxport.inc
include pgexport.inc
include vmexport.inc
include virtsym.inc
include struc.inc
include int.inc
include kmode.inc
include ldrvars.inc
include iodelay.inc
.list

        ModName , FNO_PROFILE_ASM
        CpuMode 386

        ReferFlat       _DosModMte,BYTE
        ReferFlat       _mte_h,DWORD
        ReferFlat       _R0CS,WORD
        ReferFlat       fIdle,BYTE

        ReferGDT        GDT_PTDA,WORD

        ReferTask       sPCB,WORD           ; process profile ctl block segment
        ReferTask       ptda_module,WORD    ; process hmte
        ReferTask       ptda_ldtaddr,DWORD  ; flat address of ldt

        EXTRNFAR        SuBuff
        EXTRNFAR        SuByte
        EXTRNFAR        SuWord
        EXTRNFAR        DStrLen
        EXTRNFAR        FuStrLenZ
        EXTRNFAR        SegLock
        EXTRNFAR        SegUnlock

;;;     BUGBUG 8-21-89 rickdew num_to_sel stub removed.
;;;     EXTRNFAR        num_to_sel
;;;     EXTRNFAR        MemMapMTEAlias
;;;     EXTRNFAR        MemUnmapMTEAlias
        EXTRNFAR        SegAlloc
        EXTRNFAR        SegRealloc
        EXTRNFAR        SegFree
        EXTRN           _VMAllocMem:near
        EXTRN           _VMFreeMem:near


SHIFT_FACTOR    equ     2       ; Collect stats at DWORD granularity
SHIFT_MASK      equ     (0FFFFh) and (-1 shl SHIFT_FACTOR) ; Mask for IP
PROF_FREQ       equ     2330    ; frequency = 1193180/2330 = 512 Hz
MAX_NOBJ        equ     0ah     ; number of flat regions for examination
MAX_KSEL        equ     14h     ; number of use16 segments for examination
TP_CH0          equ     40h     ; channel 0 data port
TP_CMD          equ     43h     ; command register port
TC_DA_BIN       equ     00h     ; data is in binary form
TC_MD_SQU       equ     04h     ; timer mode 2
TC_OP_HILO      equ     030h    ; read/write low byte, then high byte
TC_CH_0         equ     000h    ; channel 0
VMPR_FLAG1      equ   VMAC_LOCANY OR VMAC_ALIGNPAGE OR VMAC_ARENASYS
VMPR_FLAG2      equ   PG_R OR PG_W OR PG_RESIDENT OR PG_ZEROFILL
VMPR_FLAG       equ   VMPR_FLAG1 OR VMPR_FLAG2
MAX_DUMP        equ   0fffch    ; 64K-4 (4 is cb at head of data buffer)

;*  flat object record

pcbf_s  struc
pcbf_laddr      dd      ?       ; start linear address
pcbf_cb         dd      ?       ; size of region
pcbf_ord        dw      ?       ; ordinal of object
pcbf_fKernel    dw      ?       ; TRUE => kernel flat object
pcbf_offDmp     dd      ?       ; offset from pObjData of counts for this obj
pcbf_cbDump     dd      ?       ; next byte to dump (for multi-call dumping)
pcbf_s  ends


;*  Profile Control Block
;
;   NOTE: pcb_cticKernel contains sum of all segment and flat ticks in the
;         kernel.  If PF_VERBOSE is set, pcb_cticFlat sums only non-kernel
;         flat tics, otherwise it sums ALL flat tics.  In the latter case,
;         pcb_cticKernel - pcb_cticFlat = segmented kernel ticks.

pcb_s  struc
pcb_selFlat     dw      ?       ; flat selector
pcb_cpcbf       dw      ?       ; number of entries in pcb_apcbf
pcb_pObjData    dd      ?       ; pointer to data for all regions
pcb_cbObjData   dd      ?       ; size of data for all regions
pcb_apcbf       db  MAX_NOBJ * size pcbf_s DUP (?)  ; data for flat regions
pcb_cntSels     dw      ?       ; number of selectors
pcb_cntKSels    dw      ?       ; number of kernel selectors
pcb_aordKSel    dw  MAX_KSEL dup (?) ; list of kernel selector ordinals
pcb_cticIdle    dd      ?       ; count of idle tics
pcb_cticV86     dd      ?       ; count of V86 mode ticks
pcb_cticFlat    dd      ?       ; count of other flat ticks
pcb_cticGDT     dd      ?       ; count of other GDT tics
pcb_cticLDT     dd      ?       ; count of other LDT tics
pcb_cticKernel  dd      ?       ; count of kernel tics; even in PF_VERBOSE mode
pcb_offNKSels   dw      ?       ; offset of start of non-kernel selectors
pcb_flags       dw      ?       ; record PF_PROFON:1,PF_VERBOSE:1, PF_KERNEL:1, PF_CALLDD:1, PF_USER:1
pcb_s  ends

;***    pcb_flags
;
;       These flags are set at ProfInit time to control what profiling
;       work is actually done.
;
;       PF_PROFON     true => enable profiling
;       PF_VERBOSE    true => collect detailed kernel tic counts
;       PF_KERNEL     true => do kernel-support profiling
;       PF_CALLDD     true => call profiling device driver if installed
;       PF_USER       true => use user pcb else use system pcb
;
; PF_KERNEL and PF_CALLDD bits select doing kernel-support profiling or
; device driver profiling or both.
;
; PF_KERNEL      PF_CALLDD
;    0             0            ; ILLEGAL
;    0             1            ; Do only device driver profiling
;    1             0            ; Do only kernel profiling
;    1             1            ; Do both

PF_RGF  record  PF_PROFON:1, PF_VERBOSE:1, PF_KERNEL:1, PF_CALLDD:1, PF_USER:1


N_PCB_SCALARS   equ     6       ; counts in PCB

BREAK <Profile Data>
DATA    segment
EVEN

lpProfileDD     dd      0       ; initialize to null pointer

        public  sSysPCB
sSysPCB         dw      0       ; system Profile Control Block

sProfileDS      dw      0

; cProfCall is incremented by ProfOn and decremented by ProfOff.
; It is used to balance ProfOn and ProfOff calls. It's initialized to
; -1 because it's easy to detect the first ProfOn (zero flag set) and
; the last ProfOff (sign flag set).
        public cProfCall
cProfCall       dw      -1

        public  achMod
achMod      db  10 dup (?)      ; 16:16 space for module name


DATA    ENDS


        FARCODE LOW
CODE    SEGMENT

;***    DoProfile - profile timer tic
;
;       ENTRY   (DS) = DOSGROUP
;               EBP  = ISF pointer
;
;       EXIT    ZF Set, no profiling done
;               ZF Clear, profiling was done
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

        assume ds:DOSGROUP,es:nothing,ss:nothing
Procedure   DoProfile, far16
        xor     bx,bx                   ; (bx) = 0, profiling not being done
        mov     ax,sSysPCB              ; (ax) = system PCB
        or      ax,ax                   ; System profiling?
        jz      dp1                     ;   No.

        mov     es,ax                   ; (es) = system PCB
    assume  es:nothing
        call    CountTic                ; Count tic for system
        mov     bx,1                    ; profiling IS being done

dp1:    mov     ax,GSEL GDT_PTDA
        mov     es,ax                   ; (es) = PTDA of current process
    assume  es:TASKAREA

        mov     ax,sPCB                 ; (ax) = process PCB
        or      ax,ax                   ; Process profiling?
        jz      dpx                     ;   No.

        mov     es,ax                   ; (es) = process PCB
    assume  es:nothing
        call    CountTic                ; Count tic for process
        mov     bx,1                    ; profiling IS being done

dpx:    or      bx,bx                   ; Set ZF to indicate profile state
        RETP
EndProc     DoProfile


;***    CountTic - collect data for tick
;
;       ENTRY   (ES)    = Profile Control Block selector
;               (DS)    = DOSGROUP
;               (EBP)   = ISF frame
;
;       EXIT    none
;
;       USES    ax,bx,cx,dx,si,di,es,flags
;
;       NOTES   We use the following algorithm to determine which counter
;               to increment:
;                   if (profiling not on)
;                       exit
;                   if (system is idle)
;                       increment IDLE count;
;                   else
;                       call flatTick
;                       if CS was not use16 segment
;                           call use16Tick(cs, ip)
;                       endif
;                   endif
;

    assume ds:DOSGROUP,es:nothing,ss:nothing
Procedure   CountTic,NEAR

    assume  es:nothing
        test    es:[pcb_flags],mask PF_PROFON; profiling turned off?
        je      ctx                     ;   YES, exit

ct5:    test    es:[pcb_flags],mask PF_KERNEL; do kernel-support profiling?
        je      ct50                    ;   NO, skip it

ct6:    FlatContext fs,cx
        test    fs:[fIdle],-1           ; system idling?
    assume  fs:nothing
        jz      ct10                    ;   NO, examine CS:IP on stack

;*  In idle loop
        inc     es:[pcb_cticIdle]
        jmp     short ctx               ; done

ct10:   call    flatTick
        jz      ctx                     ; flatTick handled selector

        push    [ebp].isf_cs            ; cs
        push    WORD PTR [ebp].isf_eip  ; ip
        call    use16Tick               ; check 16 bit segments

;*  Should we call profile device driver
;
;   (es:0) -> PCB

ct50:   ; profiling DD added here if replaced
        ; it depended on caller's BP and probably
        ; old stack frame

ctx:    ret
EndProc     CountTic


;***    use16Tick - collect data for tick
;
;       ENTRY   (ES)    = Profile Control Block selector
;               (DS)    = DOSGROUP
;               CS:IP on stack
;
;       EXIT    none
;
;       USES    ax,bx,cx,dx,si,di,es,flags
;
;       NOTES   We use the following algorithm to determine which counter
;               to increment:
;
;                   if (CS in list)
;                       Use IP to increment count for offset in segment;
;                   else if (CS is a GDT sel)
;                       increment unknown GDT count;
;                   else
;                       increment unknown LDT count;
;                   endif
;
;               We don't keep track of overflow.  Since we are using 32-bit
;               counters, and assuming a maximum clock rate of 1KHz, we
;               can profile for 2**(32)/2**(10) = 2**(32-10) = 4,194,304 sec
;               = 48 days.  So, overflow is not much of a concern.  Note that
;               if we used 16-bit counters, however, overflow would happen
;               much more quickly: 2*(16-10) = 64 sec = 1 minute.
;
;       SPEED   We use ES to avoid reloading DS

    assume ds:DOSGROUP,es:nothing,ss:nothing
Procedure   use16Tick,NEAR

        ArgVar  dp_CSIP,DWORD           ; CS:IP of interrupted thread

        EnterProc

    assume  es:nothing
        mov     cx,es:[pcb_cntSels]     ; (cx) = length of code sel list
        mov     dx,cx                   ; (dx) = length of code sel list
        mov     ax,dp_CSIP.Segmt        ; (ax) = CS of interrupted code
        and     ax,RPL_CLR              ; (ax) = CS with RPL=0
        mov     di,size pcb_s           ; (es:di) = code sel list
        repne scasw
        jne     ut30                    ; Didn't find match

        cmp     di,es:[pcb_offNKSels]   ; Is this a kernel selector?
        ja      ut20                    ;   NO, don't inc kernel count

        inc     es:[pcb_cticKernel]     ; Count kernel tic
        test    es:[pcb_flags],mask PF_VERBOSE; Collect detail tics?
        jz      ut50                    ;   NO, skip it

;*  Get corresponding profiling data selector
;
;   (es:di-2) = ptr to matching selector
;   (dx)      = number of selectors
;
ut20:   dec     dx                      ; (dx) = n of sels - 1
        add     di,dx
        add     di,dx                   ; (es:di) = ptr to profile data sel
        mov     ax,es                   ; (ax) = PCB
        mov     es,es:[di]              ; (es) = profile data segment
    assume  es:nothing
        mov     si,dp_CSIP.Offst        ; (si) = IP of interrupted code
        and     si,SHIFT_MASK           ; (si) = offset of DWORD counter
        inc     dword ptr es:[si]       ; increment counter
        mov     es,ax                   ; (es) = PCB
        jmp     short ut50              ; exit

;*  Selector not in list
;
;   (ax) = selector with RPL=0

ut30:   test    ax,TABLE_MASK
        jz      ut40                    ; GDT selector

;*  LDT selector
        inc     es:[pcb_cticLDT]
        jmp     short ut50

;*  GDT selector
ut40:   inc     es:[pcb_cticGDT]

ut50:   LeaveProc
utx:    ret 4

EndProc     use16Tick


;***    flatTick - collect data for flat selector
;
;       ENTRY   (ES)    = Profile Control Block selector
;               (DS)    = DOSGROUP
;               (EBP)   = ISF
;
;       EXIT    ZF set   - selector was counted by flatTick
;               ZF clear - selector was not counted
;
;       USES    ax,bx,cx,dx,si,di,es,flags
;
;       NOTES   We use the following algorithm to determine which counter
;               to increment:
;
;                   if (eflags indicates V86 mode)
;                       increment V86 counter
;                   else
;                       check if CS is flat
;                       if CS is flat
;                           increment overall flat tick counter
;                           search through regions looking for one
;                               that contains eip
;                           if found increment corresponding location
;                               in data region
;                           endif
;                       endif
;                   endif
;

    assume ds:DOSGROUP,es:nothing,ss:nothing
Procedure   flatTick,NEAR
        SaveReg <ds>
        FlatContext <ds>,ax

        mov     si,1                    ; (SI) = 1 selector not counted
        test    [ebp].isf_eflag,F_VM    ; check if V86 interrupted
        je      ft10                    ; not V86 mode

        xor     si,si                   ; (SI) = 0 selector counted
        inc     DWORD PTR es:[pcb_cticV86] ; increment v86 ticks
        jmp     short ftx               ; done

ft10:   mov     ax,es:[pcb_selFlat]     ; target flat selector
        cmp     ax,[ebp].isf_cs         ; compared to current selector
        jne     ftx                     ; not flat selector

;*  Count flat tick

        xor     si,si                   ; (SI) = 0 , tick counted
        test    es:[pcb_flags],mask PF_VERBOSE ; Collect details ticks?
        jz      ft40                    ;   NO, add to flat tick scalar

;*  Set up for scan of linear ranges

        mov     cx,es:[pcb_cpcbf]       ; (CX)  = number of flat regions
        mov     di,pcb_apcbf            ; (DI)  = offset to start of objects
        mov     eax,[ebp].isf_eip       ; (EAX) = current eip

;*  Scan linear ranges
;
;   es:di -> apcbf
;   eax = laddr to find

ft20:   mov     edx,es:[di].pcbf_laddr  ; edx = start of linear object
        sub     eax,edx                 ; (EAX) = dist to start address
        jb      ftx                     ; below start of region

        cmp     eax,es:[di].pcbf_cb     ; laddr in this object?
        jae     ft30                    ;   NO, past end; continue loop

        and     eax,NOT 011b            ; eax = 4 byte counter granularity
        add     eax,es:[pcb_pObjData]   ; eax = EIP offset + start of counters
        add     eax,es:[di].pcbf_offDmp ; eax -> counter
        inc     DWORD PTR [eax]         ; increment counter
; BUGBUG 29-Jun-1989 bens Assume all flat objects in list are kernel objects!
        inc     es:[pcb_cticKernel]
; BUGBUG 29-Jun-1989 bens **END*
        xor     si,si                   ; counted
        jmp     short ftx               ; go exit

ft30:   add     eax,edx                 ; restore laddr
        add     di,size pcbf_s          ; es:di -> next pcbf
        loop    ft20

ft40:   inc     es:[pcb_cticFlat]       ; increment misc flat ticks

ftx:    or      si,si                   ; Set ZF for exit conditions
        RestoreReg <ds>
    assume  ds:nothing
        ret

EndProc     flatTick


;***    dh_RegisterProfile - set lpProfileDD vector
;
;       NOTE: DD call was removed
;
;       ENTRY   bx:ax -> Profiler handler routine
;               cx     = Device Drivers DS
;
;       EXIT    es:bx -> procedure SelToName
;               CY     = 0
;
;       USES    bx,ds,es,flags
;
;               FS and GS must be preserved (DevHelp requirement)

    ASSUME  DS:NOTHING, ES:NOTHING, SS:NOTHING
Procedure dh_RegisterProfile,NEAR

        DosContext ds
        mov     sProfileDS,cx           ; save device driver's DS
        mov     lpProfileDD.Offst,ax    ; save profiler handler pointer
        mov     lpProfileDD.Segmt,bx
        push    DOSHIGH3CODE
        pop     es                      ; (es) = CS of SelToName
    assume es:DOSHIGH3CODE
        MOVFAROFFSET bx,SelToName,DOSHIGH3CODE ; es:bx -> SelToName
        clc
        ret
EndProc dh_RegisterProfile

CODE    ENDS


BREAK   <w_DosProfile - API worker routine for system profiling>
         FARCODE HIGH3
HIGH3CODE SEGMENT

ProfDispTable    label   word
        dw      ProfAlloc
        dw      ProfClear
        dw      ProfOn
        dw      ProfOff
        dw      ProfDump
        dw      ProfFree
PROF_DISP_LEN equ ($-ProfDispTable)/2


;***    w_DosProfile - worker for System Profiling
;
;       ENTRY   (ax)    = function request
;                           0 => allocate profile buffers
;                           1 => reset profile data
;                           2 => profile on
;                           3 => profile off
;                           4 => return contents of buffer specified by (bx)
;                           5 => free profile buffers
;               (cx)    = PF_RGF Flags
;               (dx)    = parameter (usually buffer number)
;               (ds:si) = user data buffer
;               (es:di) = user name buffer (must be at least 9 bytes long)
;               CLD
;
;       EXIT    carry clear = success
;               carry set = failure
;                   (ax) = error code
;
;       USES    bx,cx,di,es,flags

        assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure w_DosProfile,HYBRID
        cmp     ax,PROF_DISP_LEN
        jae     wdpe                    ; invalid function number

        mov     bx,ax                   ; (bx) = function number
        shl     bx,1                    ; (bx) = dispatch table offset
        call    ProfDispTable[bx]       ; Call worker routine

wdpx:   ret

wdpe:   stc
        mov     ax,ERROR_INVALID_FUNCTION
        jmp     wdpx

EndProc   w_DosProfile


;***    flagToPCB - map system/user flag to Profile Control Block
;
;       ENTRY   (cx) = record ..., PF_USER:1
;
;       EXIT    carry clear, PCB present
;                   (ax) = Profile Control Block
;               cary set, no PCB
;                   (ax) = ERROR_INVALID_BLOCK
;
;       USES    ax,ds,flags

Procedure   flagToPCB
        test    cx,mask PF_USER         ; system profiling?
        jz      ftp1                    ;   YES, go get system PCB

        mov     ax,sPCB                 ; (ax) = process PCB
        jmp     short ftp2

ftp1:   DosContext <ds>
        mov     ax,sSysPCB              ; (ax) = system PCB

ftp2:   or      ax,ax                   ; PCB exist?
        jnz     ftpx                    ;   YES, normal exit (CLC)

        mov     ax,ERROR_INVALID_BLOCK
        stc                             ; no PCB, set error exit

ftpx:   ret

EndProc     flagToPCB


;***    SetPCB - Set PCB ptr based on system/user flag
;
;       ENTRY   (bx) = PCB selector
;               (cx) = record ..., PF_USER:1
;
;       EXIT    none
;
;       USES    ax,flags

    assume ds:nothing,es:nothing,ss:TASKAREA
Procedure   SetPCB
        test    cx,mask PF_USER         ; user profiling?
        jz      sp1                     ;   NO, System profiling

        mov     sPCB,bx                 ; Set process PCB
        jmp     short spx

sp1:    SaveReg <ds>
        DosContext <ds>
        mov     sSysPCB,bx              ; Set system PCB
        RestoreReg <ds>

spx:    ret
EndProc     SetPCB

;***    ProfOff - Turn off profiling
;
;       ENTRY   (cx) = record ..., PF_USER:1
;
;       EXIT    (ax) = 0, success
;
;       USES    ax,bx,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   ProfOff

        xor     ax,ax
        mov     ds,ax                   ; avoid spurious fault/swap in
        mov     es,ax                   ; avoid spurious fault/swap in
        call    flagToPCB               ; ax=PCB = flagToPCB(cx=flag)
        jc      ppoe                    ;   No PCB, go exit with error

        mov     ds,ax                   ; (ds) = PCB
    assume  ds:nothing
        test    ds:[pcb_flags],mask PF_PROFON; Is profiling already off?
        jz      ppox

        and     ds:[pcb_flags],not(mask PF_PROFON); clear PF_PROFON bit

        DosContext ds, ax
        dec     ds:cProfCall            ; if this ProfOff call is the
        jns     ppox                    ; last (balanced) ProfOff call
                                        ; reset the flags.

;* Restore timer rate to 18.2 Hz by using 0 (= 65536) as divisor
        cli
        xor     bx,bx                   ; 1193180/65536 = 18.2 Hz
        call    SetTimer0

;*  BUGBUG bens 4-jun-1989 Timer being reset to default rate, which is not
;   what VTIMER wants.  We'll fix it later.  Note that we leave the interrupt
;   enabled so that VTIMER can get it (CLOCK0n.SYS, actually).  If the
;   Clock driver is not interested, then the interrupt manager will mask
;   the interrupt off for us.
;
;       in      al,PIC1_PORT1           ; Get current mask
;       or      al,01h                  ; Make mask for IRQ 0
;       kIODelay bx
;       out     PIC1_PORT1,al           ; Set mask

        sti

ppox:   xor     ax,ax
ppoe:   ret
EndProc     ProfOff


;***    ProfOn - Turn on profiling
;
;       ENTRY   (cx) = record ..., PF_USER:1
;
;       EXIT    (ax) = 0, success
;
;       USES    ax,bx,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   ProfOn

        push    cx

        xor     ax,ax
        mov     ds,ax                   ; avoid spurious fault/swap in
        mov     es,ax                   ; avoid spurious fault/swap in
        call    flagToPCB               ; ax=PCB = flagToPCB(cx=flag)
        jc      poe                     ;   No PCB, go exit with error

        mov     ds,ax                   ; (ds) = PCB
    assume  ds:nothing
        test    ds:[pcb_flags],mask PF_PROFON; Is profiling already on?
        jnz     pox

        or      ds:[pcb_flags],mask PF_PROFON; set PF_PROFON bit

        DosContext  <ds>,ax
        inc     cProfCall               ;if this is not the first ProfOn
        jnz     pox                     ; call, then skip the mask setting

        cli

;* Enable IRQ 0

        in      al,PIC1_PORT1           ; Read in current INT mask
        and     al,0FEh                 ; mask off timer 0 bit
        kIODelay cx
        out     PIC1_PORT1,al           ; Write out current INT mask

;* Set timer rate
        mov     bx,PROF_FREQ            ; new frequency for profiling
        call    SetTimer0

        sti

pox:    xor     ax,ax
poe:    pop     cx
        ret


EndProc ProfOn


;***    ProfClear - Clear profile data
;
;       ENTRY   (cx) = record ..., PF_USER:1
;               CLD
;
;       EXIT    carry clear = success
;                       ax = 0
;               carry set = error
;                       ax = ERROR_INVALID_BLOCK
;                       ax = ERROR_INVALID_FUNCTION
;
;       USES    ax,cx,dx,si,di,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   ProfClear
        xor     ax,ax
        mov     ds,ax                   ; avoid spurious fault/swap in
        mov     es,ax                   ; avoid spurious fault/swap in
        call    flagToPCB               ; ax=PCB = flagToPCB(cx=flag)
        jc      pcx                     ;   No PCB, exit with error

        mov     ds,ax                   ; (ds) = PCB
    assume ds:nothing

        test    ds:[pcb_flags],mask PF_KERNEL ; Kernel profiling?
        jz      pce                     ;   NO, No data to clear, skip it

        mov     dx,ds:[pcb_cntSels]     ; (dx) = number of segments
        mov     si,size pcb_s           ; (ds:si) = code selectors
        add     si,dx
        add     si,dx                   ; (ds:si) = start of data selectors

;*  NOTE: If we are not doing verbose profiling, then there are no
;         data segments allocated for kernel segments.

        test    ds:[pcb_flags],mask PF_VERBOSE ; segments for kernel?
        jnz     pc10                    ;   YES, dx has correct count

        sub     dx,ds:[pcb_cntKSels]    ; dx = # of non-kernel segments
        jz      pc25                    ; Non-Verbose kernel profile only

pc10:   lodsw                           ; (ax) = profile data selector
        or      ax,ax                   ; Segment allocated?
        jz      pc20                    ;   NO, skip it

        mov     es,ax                   ; (es) = profile data segment
    assume  es:nothing
        xor     di,di                   ; (es:di) = segment to clear
        LOADSL  cx,ax                   ; (cx) = last offset
        inc     cx                      ; (cx) = number of bytes to zero
        shr     cx,1                    ; (cx) = number of words to zero
        xor     ax,ax                   ; (ax) = 0, for clearing
        rep     stosw                   ; clear

pc20:   dec     dx                      ; (dx) = number of segments remaining
        jnz     pc10                    ; More segments to clear

;*  Clear counts for flat objects

; BUGBUG 19-Jun-1989 wayneca This could be slow; should yield

pc25:   FlatContext <es>,eax
    assume  es:nothing

        mov     edi,ds:[pcb_pObjData]   ; edi -> counts for flat objects
        or      edi,edi                 ; Flat counts allocated?
        je      pc50                    ;   No, skip clearing them

        mov     ecx,ds:[pcb_cbObjData]  ; ecx = number of bytes to clear
        shr     ecx,2                   ; ecx = number of dwords to clear

        xor     eax,eax                 ; (ax) = 0, for clearing
        rep     stos dword ptr es:[edi] ; clear counts

;*  Clear dump offsets

        mov     cx,ds:[pcb_cpcbf]       ; cx = number of flat objects
        mov     di,pcb_apcbf            ; (ds:di) -> pcbf structures

pc30:   mov     ds:[di].pcbf_cbDump,eax ; reset dump offset
        add     di,size pcbf_s          ; advance to next pcbf
        loop    pc30                    ; process rest of pcbfs

;*  Clear scalar counters

pc50:   push    ds
        pop     es                      ; (es) = PCB
    assume  es:nothing

        mov     di,pcb_cticIdle         ; (es:di) = start of scalars
        mov     ecx,N_PCB_SCALARS       ; ecx = dwords to clear
        xor     eax,eax                 ; (ax) = 0, CLC
        rep     stosd                   ; Clear scalar counts
pcx:    ret

pce:    mov     ax,ERROR_INVALID_FUNCTION ; No data segment to clear
        stc
        ret

EndProc     ProfClear


;***    ProfAlloc - Allocate profile buffers
;
;       ENTRY   (CX) = record PF_PROFON:1, PF_VERBOSE:1,
;                       PF_KERNEL:1, PF_CALLDD:1, PF_USER:1
;               PF_USER = 0 - system profiling
;
;                 (DS:SI) -> module name list
;                   The module name list is a collection of asciiz
;                   library names (case not significant). The list is
;                   terminated by an empty string. E.g:
;                     db  "DosCall1",0,"VioCalls",0,"kbdcalls",0,0
;                   Buffers are allocated for the LDT code segments of
;                   these modules so they can be profiled individually.
;
;                 (DS:SI) = null (DS=SI=0)
;                   All LDT code segments are grouped into
;                   "miscellaneous LDT segments" during profiling.
;
;               PF_USER = 1 - user profiling
;                 (DS:SI) = don't care
;
;               CLD
;
;       EXIT    NONE
;       USES    EAX, EBX, ECX, EDX, EDI, ESI, DS, ES, Flags

Procedure   ProfAlloc
    assume  ds:nothing,es:nothing,ss:TASKAREA
        LocalVar    pa_fDosProf,WORD    ; DosProfile flags
        LocalVar    pa_cSel,WORD        ; number of selector remaining
        LocalVar    pa_lprgsCode,DWORD  ; points to code selector list
        LocalVar    pa_lprgsData,DWORD  ; points to data selector list
        LocalVar    pa_ac,<size vmac_s> ; return data for allocation

        EnterProc   <cx>                ; DosProfile flags
        xor     ax,ax
        mov     es,ax                   ; avoid spurious fault/swap in

; es=PCB = BuildSelList(cx=flag,ds:si->module list)

        call    BuildSelList
        jnc     pa03
        jmp     pax

pa03:   test    pa_fDosProf,mask PF_KERNEL; Are we doing kernel profiling?
        jnz     pa04                    ;   YES, go allocate memory
        jmp     pax                     ;   NO, exit

pa04:   mov     ax,es
        mov     pa_lprgsCode.Segmt,ax
        mov     pa_lprgsData.Segmt,ax

;*  Allocate flat object

        test    es:[pcb_flags],mask PF_VERBOSE; collect kernel histogram?
        jz      pa06                    ; No, skip flat objects

        mov     ecx,es:[pcb_cbObjData]  ; ecx = flat object size
        SaveReg <ds,es,dx>

        FlatContext <ds,es>,ax

        xor     eax,eax
        .errnz  HOBNULL
        SSToDS  edx,[pa_ac]

;       rc = VMAllocMem(cb, cbCommit, fl, hobPTDA, hobOwner,
;                       hobMTE, flSelMgr, blkno, pac)

        CCALL32 _VMAllocMem,<ecx,ecx,VMPR_FLAG,eax,PROFOWNER,eax,eax,eax,edx>
        RestoreReg <dx,es,ds>
    assume ds:nothing,es:nothing

        or      eax,eax                 ; error?
        jz      short pa05              ;   NO, keep going

        stc                             ;   YES, return failure
        jmp     pa50

pa05:   mov     eax,[pa_ac].ac_va
        mov     es:[pcb_pObjData],eax   ; start of flat data buffer

;*  Do segment allocation processing

pa06:   mov     cx,es:[pcb_cntSels]     ; (cx) = number of selectors
        mov     bx,cx                   ; (bx) = number of selectors left

        mov     si,size pcb_s           ; (ax:si) -> list of code selector
        test    es:[pcb_flags],mask PF_VERBOSE; collect kernel histogram?
        jnz     pa07                    ; allocate data seg. for kernel codes

        mov     si,es:[pcb_offNKSels]   ; (ax:si) -> list of non-kernel sels
        sub     bx,es:[pcb_cntKSels]    ; (bx) = number of non-kernel sels

; BUGBUG 26-Jun-1989 wayneca  test added if no dll's are added this bombed
pa07:   or      bx,bx                   ; any selectors to allocate
        jz      pax

        mov     pa_lprgsCode.Offst,si   ; lprgsCode -> list of code selectors
        mov     pa_cSel,bx              ; pa_cSel = number of selectors left
        add     si,cx
        add     si,cx                   ; (ax:si) -> list of data selectors
        mov     pa_lprgsData.Offst,si   ; lprgsData -> list of data selectors

;*  lprgsCode -> next code selector for which to allocate data segment
;   lprgsData -> word to receive data selector
;   pa_cSel  = number of selector still to be processed

pa10:   lds     si,pa_lprgsCode         ; (ds:si) -> code selector
        lodsw                           ; (ax) = code selector
        mov     pa_lprgsCode.Offst,si   ; lprgsCode -> next code selector
        LOADSL  bx,ax                   ; (bx) = segment limit
        jz      pa30                    ;   LSL success

;* If we can't get the limit from the descriptor, we'll have to get it
;  from the MTE.
;  If we had stored the limit while constructing the selector list, we
;  would be able to eliminate this extra call to MTEFindSel (which
;  is slow). But since ProfAlloc doesn't have to be fast, it's easier
;  to do it this way (no need to allocate space to store the limits).

; ds:edi=ste = MTEFindSel(bx=selector)
        mov     bx,ax                   ; bx = selector to find
        call    MTEFindSel              ; ax = size
    assume  ds:nothing
        jc      pa40                    ;   CAN'T FIND SELECTOR, abort

        mov     bx,ax                   ; (bx) = size in bytes
        dec     bx                      ; (bx) = limit

pa30:   inc     bx                      ; (bx) = size in bytes
        xor     ax,ax                   ; (ax:bx) = size of segment in bytes
        add     bx,not SHIFT_MASK       ; round up to granularity
        adc     ax,0                    ; do carry
        and     bx,SHIFT_MASK           ; (ax:bx) = rounded size of segment

        mov     cl,SA_GDT               ; (cl) = allocate GDT
        mov     ch,D_DATA0              ; (ch) = ring 0 data
        mov     si,HF_FIXED             ; (si) = fixed memory
        mov     di,PROFOWNER            ; (di) = owned by system
        CALLFAR SegAlloc                ; bx=selector = SegAlloc(...)
        jc      pa50                    ; Alloc failed

        xchg    ax,bx                   ; (ax) = selector
        les     di,pa_lprgsData         ; (es:di) -> data selector slot
        stosw                           ; Store data selector
        mov     pa_lprgsData.Offst,di   ; lprgsData -> next data selector slot
        dec     pa_cSel                 ; more segments to allocate?
        jnz     pa10                    ;   YES, do it

        mov     cx,pa_fDosProf          ; (cx) = DosProfile flags
        call    ProfClear               ; Zero counts
        jmp     short pax

;* this probably would not happen
pa40:   mov     cx,pa_fDosProf
        call    ProfFree
        mov     ax,ERROR_INVALID_SELECTOR
        jmp     short pa60

pa50:   mov     cx,pa_fDosProf          ; (cx) = DosProfile flags
        call    ProfFree                ; Free all memory allocated so far
        mov     ax,ERROR_NOT_ENOUGH_MEMORY
pa60:   stc

pax:    LeaveProc
        ret

EndProc     ProfAlloc


;***    BuildSelList - Create PCB and fill with CS list
;
;       ENTRY   (cx) = PF_RGF flags
;                      PF_USER = 0 - system profiling
;                          ds:si -> module list or null
;                                    (see ProfAlloc header)
;                      PF_USER = 1 - user profiling
;               (ds:si) -> list of dll names or else 0
;
;               CLD
;
;       EXIT    (CF) = 0, success
;                   (AX) = 0
;                   (ES) = PCB
;               (CF) = 1, failure
;                   (AX) = error code
;
;       USES    ax,bx,cx,dx,di,si,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   BuildSelList
        LocalVar        bsl_lpszMList,DWORD ; pointer to module list
        LocalVar        bsl_fDosProf,WORD   ; DosProfile flags

        EnterProc       <ds,si, cx>     ; _lpszMList=ds:si, _fDosProf=cx
        SaveReg <ds>
        call    ProfFree                ; ProfFree(cx=flag)
        RestoreReg <ds>                 ; (es) = PCB

;*  Allocate PCB

        mov     ax,1
        xor     bx,bx                   ; (ax:bx) = 64K segment
        mov     cl,SA_GDT               ; (cl) = allocate GDT
        mov     ch,D_DATA0              ; (ch) = ring 0 data
        mov     si,HF_FIXED             ; (si) = fixed memory
        mov     di,PROFOWNER            ; (di) = owner
        SaveReg <ds>
        CALLFAR SegAlloc                ; (bx)=selector = SegAlloc(...)
        RestoreReg <ds>                 ; (es) = PCB
        jnc     bsl03                   ; Alloc suceeded
        jmp     bsl30                   ; Alloc failed, go exit

bsl03:  mov     es,bx
    assume  es:nothing
        mov     cx,bsl_fDosProf         ; (cx) = DosProfile flag
        and     cx,not(mask PF_PROFON)  ; Profiling is off to start
        mov     es:[pcb_flags],cx       ; Initialize pcb_flags
        call    SetPCB                  ; SetPCB(bx=sel cx=flag)
        xor     eax,eax
        mov     es:[pcb_cntSels],ax     ; pcb_cntSels = no selectors so far
        mov     es:[pcb_pObjData],eax   ; no flat data buffer
        mov     es:[pcb_cpcbf],ax       ; no flat data objects
        test    cx,mask PF_KERNEL       ; Are we doing kernel profiling?
        jz      psl25                   ;  NO! skip the rest

;*  Add kernel code selectors to list
        call    initKProf               ;  initialize kernel segments, flat

        test    cx,mask PF_USER         ; user profiling?
        jnz     bsl10                   ;   YES, go scan process' LDT

;*  add LDT selectors from the specified modules
        mov     ax,bsl_lpszMList.Segmt
        mov     si,bsl_lpszMList.Offst  ; ax:si -> module list
        mov     bx,ax
        or      bx,si                   ; is ax:si null?
        jz      bsl20                   ;   YES, done adding selectors

; BUGBUG 23-Jun-1989 wayneca  disabled call since AddLibLDTs has
; has an internal error placed at it's start.
;       call    AddLibLDTs              ; add library LDT code selectors
;       jc      bsl40                   ;   ERROR, cleanup and exit
        jmp     short bsl20

bsl10:  call    AddProcLDTs             ; add process LDT code selectors

;*  Zero data selector table
;
;   This allows ProfFree to behave nicely if ProfAlloc cannot allocate
;   all the data segments.
;
;   (es:di) = first data selector

bsl20:  mov     cx,es:[pcb_cntSels]     ; (cx) = number of selectors
        xor     ax,ax                   ; (ax) = 0, null selector
        rep stosw                       ; Zero data selectors

;*  Shrink PCB
;
;   We need 2 words per selector: 1 word for CS and 1 word for data selector

psl25:  mov     bx,es:[pcb_cntSels]     ; (bx) = number of code selectors
        shl     bx,2                    ; (bx) = size of selector tables
        add     bx,size pcb_s           ; (bx) = size of PCB
        xor     ax,ax                   ; (ax:bx) = new size
        mov     di,es                   ; (di) = PCB
        SaveReg <ds,es>
        CALLFAR SegRealloc              ; SegRealloc(ax:bx=size,di=sel)
        RestoreReg <es,ds>              ; (es) = PCB
        jc      bslie                   ;   Realloc failed
        xor     ax,ax                   ; (ax) = success
        jmp     short bslx

bsl30:  mov     ax,ERROR_NOT_ENOUGH_MEMORY
        jmp     bslx

bsl40:  SaveReg <ax>                    ; save error code
        mov     cx,bsl_fDosProf         ; (cx) = DosProfile flag
; ProfFree(cx=flag)
        call    ProfFree                ; remove partially done PCB
        RestoreReg <ax>                 ; restore error code
        stc                             ; set error flag

bslx:   LeaveProc
        ret

bslie:  InternalError <BuildSelList: could not realloc profile control block>
EndProc     BuildSelList



;***    initKProf - initialize kernel segment and flat ranges
;
;       Builds list of use16 kernel segments and flat objects
;       Initializes pcb_selFlat, pcb_cpcbf, pObjData = 0,
;       pcbObjData = 0, pcb_cntSels, pcb_cntKSels, pcb_offNKSels
;
;       ENTRY   (es) = PCB
;               CLD
;
;       EXIT    es:di -> next selector slot in PCB
;
;       USES    ax,bx,dx,si,eflags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   initKProf,near

LocalVar  ikp_pord,       WORD          ; pointer to kernel selector ordinals

        EnterProc
        SaveReg <cx,si,ds,es>
        push    es
        pop     ds
    assume  ds:nothing                  ; ds -> PCB (makes code smaller)
        FlatContext <es>,eax            ; es = FLAT (used less than PCB)

        mov     eax,offset FLAT:_DosModMte ; (eax) = DOS MTE
        mov     eax,[eax].mte_swapmte   ; eax = pointer to swappable mte

        mov     ebx,es:[eax].smte_objtab; ebx = seg/obj table start
        mov     ecx,es:[eax].smte_objcnt; ecx = object count
        mov     edx,1                   ; edx = object ordinal

        mov     di,size pcb_s           ; es:di -> code selector list
        mov     si,pcb_apcbf            ; es:si -> flat object list
        mov     ikp_pord,pcb_aordKSel   ; start of kernel selector ordinal list

        xor     eax,eax
        mov     ds:[pcb_cbObjData],eax  ; initialize total size of flat objects
        mov     ds:[pcb_cpcbf],ax       ; init count of flat objects
        mov     ds:[pcb_cntKSels],ax    ; init count of kernel selectors

;*  Entry
;       es:ebx -> object table entry
;       ecx = count of objects still to process
;       edx = object ordinal
;       ds:di -> 16-bit code selector table to fill in
;       ds:si -> flat object table to fill in

ikp10:  mov     eax,es:[ebx].ote_flags  ; eax = object flags
        test    eax,OBJEXEC             ; Code?
        jz      ikp30                   ;   NO, skip

        test    eax,OBJBIGDEF           ; 32-bit object?
        jz      ikp20                   ;   NO, make entry in selector list

;*  32-bit object
        inc     ds:[pcb_cpcbf]          ;   YES, make entry in flat list
        mov     eax,es:[ebx].ote_base   ; eax = object base
        mov     ds:[si].pcbf_laddr,eax  ; set base

        mov     eax,ds:pcb_cbObjData    ; ebx = offset of counters in flat obj
        mov     ds:[si].pcbf_offDmp,eax ; set dump offset for this object

        mov     eax,es:[ebx].ote_size   ; eax = object size
        add     eax,3                   ; round up to DWORD size
        and     eax,NOT 3               ;
        mov     ds:[si].pcbf_cb,eax     ; set size

        add     ds:[pcb_cbObjData],eax  ; accumulate total size

        xor     eax,eax
        mov     ds:[si].pcbf_cbDump,eax ; no bytes dumped, yet.
;BUGBUG 14-Jul-1989 bens Assume OTE ordinal <64K
        mov     ds:[si].pcbf_ord,dx     ; set ordinal
        mov     ds:[si].pcbf_fKernel,1  ; mark KERNEL flat object
        jmp short ikp30

;*  16:16 object

; BUGBUG 16-Jun-1989 bens Get kernel selector from object handle highword
ikp20:  mov     ax,es:[ebx].ote_selector

; BUGBUG 28-Jun-1989 bens Hard-code kernel init segments
;
; There are no consistent flags in the OTE to indicate the init segments!
; Either we should provide such flags, or the OTEs for the init segments
; should be whacked to indicate that they are no longer valid!
;
        cmp     ax,DOSINITDATA          ; init segment?
        je      ikp30                   ;   YES, skip
        cmp     ax,DOSINITR3CODE        ; init segment?
        je      ikp30                   ;   YES, skip

        mov     ds:[di],ax              ; store selector
        inc     di
        inc     di                      ; advance pointer

        inc     ds:[pcb_cntKSels]       ; Count kernel selector

        SaveReg <bx>
        mov     bx,[ikp_pord]           ; bx -> kernel ordinal list
        mov     ds:[bx],dx              ; set ordinal
        inc     bx
        inc     bx                      ; advance pointer
        mov     ikp_pord,bx             ; save pointer
        RestoreReg <bx>

;*  Loop tail

ikp30:  add     ebx,size ldrote_s       ; advance OTE pointer
        inc     edx                     ; advance object ordinal
        dec     ecx                     ; !target too far back for loop!
        jnz     ikp10                   ; do next object

;*  Finish initialization of kernel object stuff

        mov     ds:[pcb_offNKSels],di   ; Save pointer to first non-kernel sel.
        mov     ax,ds:[pcb_cntKSels]    ; Number of kernel code selectors
        mov     ds:[pcb_cntSels],ax     ; Number of code selectors so far
        mov     ax,es:_R0CS
        mov     ds:[pcb_selFlat],ax     ; Save flat selector

        RestoreReg <es,ds,si,cx>        ; es => PCB
    assume ds:nothing,es:nothing
        LeaveProc
        ret

EndProc initKProf



;***    AddProcLDTs - add code selectors from current process' LDT
;
;       ENTRY   es:di -> next slot in PCB for a code selector
;               es:[pc_cntSels] = number of selectors already in PCB
;               CLD
;
;       EXIT    code selectors of current process' LDT added to PCB
;               es:di -> next slot in PCB for code selector
;               es:[pc_cntSels] = number of selectors in PCB
;
;       USES    eax,edx,esi,di,ds,flags

Procedure AddProcLDTs,near
        assume ds:nothing,es:nothing,ss:TASKAREA
;*  Scan LDT, store all code selectors in profile control segment
;
; BUGBUG 20-Jun-1989 bens Assume LDT 'holes' are zero-filled
;
;       In 386 OS/2, LDTs are full size (though they may have uncommitted
;       regions).  We depend upon the kernel mapping in zero-fill,
;       discardable pages when we page fault touching one of these
;       pages.

        FlatContext ds,ax

        mov     edx,ss:ptda_ldtaddr     ; (edx) = flat base of ldt
        mov     esi,edx                 ; (ds:esi) = first LDT descriptor
        mov     ecx,edx
        add     ecx,(size desctab)*SEL_MAXDESC  ; End of LDT Table

;   (esi) = flat pointer to LDT descriptor to examine
;   (es:di) = next entry in profile control table for a code selector
;   (edx) = flat pointer to base of ldt

apl10:  test    ds:[esi].d_access,D_CODE
        jz      apl20                   ; Not a code selector

; check to make sure 16 bit
        test    ds:[esi].d_attr,D_COPER32
        jnz     apl20                   ; 32 bit selector

        mov     eax,esi                 ; (eax) = pointer to selector
        sub     eax,edx                 ; (ax) = selector
        or      ax,TABLE_MASK           ; (ax) = selector with LDT bit set
        stosw                           ; Add selector to table
        inc     es:[pcb_cntSels]        ; Increment count

apl20:  add     esi,size desctab        ; (ds:si) = next LDT descriptor
        cmp     esi,ecx                 ; more descriptors to scan?
        jl      apl10                   ;   YES, loop

        ret
EndProc AddProcLDTs


;***    AddLibLDTs - collect LDT selectors from given modules
;
;       ENTRY   es    -> profile control block
;               ax:si -> module name list
;               es:[pc_cntSels] = number of selectors in PCB already
;               CLD
;
;       EXIT    carry clear - success
;                 LDT code selectors of named modules added to PCB
;                 es:[pc_cntSels] = number of selectors in PCB
;                 es:di -> next entry in PCB for code selector
;               carry set - error
;                 ax = ERROR_INVALID_SELECTOR - bad user long pointer
;                      ERROR_BAD_ENVIRONMENT - bad module list format
;                      ERROR_FILENAME_EXCED_RANGE - module name too long
;                      ERROR_MOD_NOT_FOUND - can't find given module
;
;       USES    ax,bx,cx,dx,di,si,ds,flags
;
;       PSEUDO CODE:
;
;       lock module name list segment (user's segment)
;       if (lock failed)
;         return ERROR_INVALID_SELECTOR
;       validate module name list (for protection and data format)
;       if (validation failed)
;         return ERROR_BAD_ENVIRONMENT
;       for (modname = each asciiz string in module name list) {
;         if (length of modname too long)
;           return ERROR_FILENAME_EXCED_RANGE
;         thisMTE = find_module(modname)
;         if (find_module failed)
;           return ERROR_MOD_NOT_FOUND
;         for (fsn=1 to number of file selectors in thisMTE) {
;           sel = num_to_sel(thisMTE,fsn)
;           if (num_to_sel failed)
;             InternalError
;           if (sel is a code selector) and
;              (sel is not already in PCB) then {
;             add sel to profile control block
;             increment pc_cntSels
;           }
;         }
;       }

    assume ds:nothing,es:nothing
Procedure       AddLibLDTs,near

;BUGBUG 20-Jun-1989 bens Needs updating for 32-bit support, 16-bit broken
    InternalError <AddLibLDTs: not supported in 386 OS/2, yet>

ifdef DISABLE_CODE
        LocalVar all_sPCB,WORD          ; -> PCB
        LocalVar all_lpszMList,DWORD    ; -> module list
        LocalVar all_hUserLock,DWORD    ; lock handle of user segment

        EnterProc <es, ax,si>           ; _pPCB=es, _lpszMList=ds:si

;* lock user data segment
        xor     bx,bx                   ; short duration, block
; ax:bx = handle = SegLock(ax=selector, bx=flags)
        CALLFAR SegLock                 ; lock user data segment
        jc      all35                   ;   ERROR, error exit

        mov     all_hUserLock.loword,ax ; save lock handle part A
        mov     all_hUserLock.hiword,bx ; save lock handle part B

;* make sure the data can be accessed (no fault) and is terminated with
;  a double zero byte within 32K
        mov     bx,all_lpszMList.Segmt
        mov     di,all_lpszMList.Offst  ; bx:di -> user data
; cx=data size=FuStrLenZ(bx:di->user data)
        CALLFAR FuStrLenZ               ; touch user data
        jc      all65                   ;   ERROR, cleanup and exit

all10:  lds     si,all_lpszMList        ; ds:si -> module name list
        CALLFAR DStrLen                 ; cx=length of module name + 1
        add     all_lpszMList.Offst,cx  ; _lpszMList points to next name
        dec     cx                      ; cx=length of module name
        jz      all70                   ;   NULL STRING, we're done (CLC)
        cmp     cx,MAX_NAME_LEN         ; is module name too long?
        ja      all40                   ;   YES, error


; ax=mte handle=find_module(ds:si->modname, cx=length, al=type, ah=class)
; search for global DLL modules only.

        mov     ax,EXT_LIBRARY + (256 * CLASS_PROGRAM)
        CALLFAR find_module
        jc      all50                   ;   ERROR, return error
        or      ax,ax                   ; found module?
        jz      all50                   ;   NO, return error

; de-reference MTE handle in ax
        mov     si,ax                   ; si=MTE handle
        xor     ax,ax                   ; use temporary GDT selector
        CALLFAR MemMapMTEAlias          ; ax=selector to MTE
        mov     ds,ax                   ; ds = selector to MTE

; for (dx=1 to number of segments in MTE) do
        mov     dx,1

; ax=selector, si->ste entry = num_to_sel(ax=selector number, ds->MTE)
all20:  mov     ax,dx                   ; ax=segment number, ds->MTE

;;;     BUGBUG 8-21-89 rickdew num_to_sel stub removed.
;;;     CALLFAR num_to_sel              ; ax=selector, si->ste entry
        jz      allie1                  ;   ERROR, panic

;* ax=potential selector to be added
;  si->ste entry

; only add if code selector
        mov     bx,ds:[si].ste_flags    ; selector type flags
        and     bx,TYPE_MASK            ; mask off other attributes
        cmp     bx,CODE_TYPE            ; code selector?
        jne     all30                   ;   NO, skip it

        and     ax,RPL_CLR              ; mask off ring bits

; ignore duplicate selector
        mov     es,all_sPCB             ; es->PCB
        mov     di,size pcb_s           ; es:di->start of selector list
        mov     cx,es:[pcb_cntSels]     ; cx=#selectors already in PCB
        repne   scasw                   ; selector already in PCB?
        je      all30                   ;   YES, examine next selector

;* es:di -> next slot in PCB to store selector

; add selector to PCB
        stosw                           ; store the selector
        inc     es:[pcb_cntSels]        ; update #selectors in PCB

all30:  inc     dx                      ; dx = next segment number
        cmp     dx,ds:mte_segtbl_cnt    ; done?
        jbe     all20                   ;   NO, process next selector

; unmap and free the MTE selector
        mov     ax,ds                   ; ax = selector to MTE
        CALLFAR MemUnmapMTEAlias
        jmp     all10                   ; process next module name

; error exits
all35:  mov     ax,ERROR_INVALID_SELECTOR       ; bad user pointer
        jmp     short allx

all40:  mov     ax,ERROR_FILENAME_EXCED_RANGE   ; module name too long
        jmp     short all60

all50:  mov     ax,ERROR_MOD_NOT_FOUND  ; module not found
all60:  stc
        jmp     short all80

all65:  mov     ax,ERROR_BAD_ENVIRONMENT        ; bad module list format
        jmp     short all80

all70:  mov     es,all_sPCB             ; es->PCB
        mov     di,es:[pcb_cntSels]     ; di=# of selectors in PCB
        add     di,di                   ; adjust for word pointers
        add     di,size pcb_s           ; es:di->start of selector list

; exit point for error/normal condition
all80:  pushf                           ; save carry flag
        SaveReg <ax,di,es>              ; preserve exit conditions
        mov     ax,all_hUserLock.loword ; lock-handle part A
        mov     bx,all_hUserLock.hiword ; lock-handle part B
        CALLFAR SegUnlock               ; unlock user segment
        RestoreReg <es,di,ax>           ; restore exit conditions
        popf                            ; restore carry flag

allx:   LeaveProc
        ret

allie1: InternalError <AddLibLDTs: invalid selector>
endif

EndProc         AddLibLDTs


;***    ProfFree - Free profile control block and data segments
;
;       ENTRY   (cx) = record ..., PF_USER:1
;               CLD
;
;       EXIT    (AX) = 0, success
;
;       USES    ax,bx,dx,di,si,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   ProfFree
        push    ds
        xor     ax,ax
        mov     ds,ax                   ; avoid spurious fault/swap in
        mov     es,ax                   ; avoid spurious fault/swap in
        call    ProfOff                 ; Make sure profiling is off
        call    flagToPCB               ; ax=PCB = flagToPCB(cx=flag)
        jc      pfx                     ; No PCB allocated

        mov     ds,ax                   ; (ds) = PCB
    assume  ds:nothing
        xor     bx,bx                   ; (bx) = NULL, disable profiling
        call    SetPCB                  ; SetPCB(bx=sel cx=flag)
        test    ds:[pcb_flags],mask PF_KERNEL; Are we doing kernel profiling?
        jz      pf25                    ;  NO! no data segment to free

        mov     cx,ds:[pcb_cntSels]     ; (cx) = number of segments

        mov     si,size pcb_s           ; (ds:si) = profile selectors
        add     si,cx
        add     si,cx                   ; (ds:si) = start of data selectors

pf10:   lodsw                           ; (ax) = data segment to free
        or      ax,ax
        jz      pf20                    ; Don't deallocate NULL selector

        SaveReg <cx,si,ds>
        CALLFAR SegFree                 ; SegFree(ax=sel)
        RestoreReg <ds,si,cx>

pf20:   loop    pf10                    ; More selector(s) to free

; free flat object  ####(2)
        mov     eax,ds:[pcb_pObjData]   ; flat object start address
        or      eax,eax
        je      pf25                    ; nothing to free

        SaveReg <ds,es,cx,dx>
        mov     eax,ds:[pcb_pObjData]   ; address of object
        FlatContext <ds,es>,ax

;       rc = VMFreeMem(p, hobptda, fl)

        CCALL32 _VMFreeMem,<eax, HOBNULL, VMF_DERESERVE>
        RestoreReg <dx,cx,es,ds>
    assume ds:nothing, es:nothing

pf25:   mov     ax,ds                   ; (ax) = PCB to free
        xor     si,si                   ; si = 0
        mov     ds,si                   ; ds = 0
        CALLFAR SegFree                 ; SegFree(ax=sel)
pfx:    pop     ds
        xor     ax,ax                   ; (ax) = success
        ret
EndProc     ProfFree


;***    ProfTerm - Clean up profiling resources for terminating process
;
;       Free profile control block and data segments when profiling
;       process terminates.
;
;       ENTRY   (SS) = TASKAREA
;
;       EXIT    none
;
;       USES    ax,bx,cx,dx,di,si,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   ProfTerm
        cld
        mov     cx,mask PF_USER
        call    ProfFree
        ret
EndProc     ProfTerm


;***    ProfDump - Dump a profile buffer to user
;
;       ENTRY   (cx)    = record ..., PF_USER:1
;               (DX)    = data buffer number
;                           0    => scalar data
;                           1..n => segment data
;
;               (DS:SI) = user data buffer
;               (ES:DI) = user name buffer (must be at least 9 bytes long)
;
;       EXIT    (CF) = 0, success
;                   (AX) = 0
;                   (BX) = segment number (1 based)
;                   User data buffer filled in
;                   User name buffer filled in
;                           word 0 contains length of string
;               (CF) = 1, failure
;                   (AX) = error code
;
;       USES    ax,bx,cx,dx,di,si,ds,es,flags
;
;       WARNING If the "data buffer" is not large enough, the calling
;               process will be terminated.  The caller should provide
;               a 64K segment; this is sufficient because the linker
;               only makes 64K-16 byte code segments due to 286 bug, and we
;               only need size of code segment + 4 bytes.

unkLdtName  db  8,"LDT_UNKN"    ; Length, name

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   ProfDump

        LocalVar    pd_pName,DWORD      ; far ptr to name return buffer
        LocalVar    pd_bufNum,WORD      ; data buffer number
        LocalVar    pd_PCB,WORD         ; PCB selector
        LocalVar    pd_cntKSels,WORD    ; number of kernel selectors

        EnterProc   <es,di, dx>

        mov     bx,ds
        mov     di,si                   ; (bx:di) = user data buffer
        call    flagToPCB               ; ax=PCB = flagToPCB(cx=flag)
        jnc     pd3
        jmp     pde                     ; No PCB

pd3:    mov     pd_PCB,ax               ; Save PCB
        mov     ds,ax                   ; (ds) = PCB
    assume  ds:nothing

        test    ds:[pcb_flags],mask PF_KERNEL ; Kernel profiling?
        jne     pd5
        jmp     pde2                    ;   NO,  No data to dump!

pd5:    mov     ax,ds:[pcb_cntKSels]    ; (ax) = number of kernel selectors
        mov     pd_cntKSels,ax          ; pd_cntKSels = number of kernel sels

;*  Check for scalar buffer vs. segment buffer

        dec     dx                      ; (dx) = buffer number - 1
        jns     pd10                    ;   Segment buffer

;*  Scalar buffer

        mov     ax,N_PCB_SCALARS*4+2    ; (cx) = # of scalar and flag bytes
        CALLFAR SuWord                  ; SuWord (ax=word bx:di=dst)
        jc      pdx                     ; Bad user address
        xor     ax,ax                   ; Make it a dword
        CALLFAR SuWord                  ; SuWord (ax=word bx:di=dst)
        jc      pdx                     ; Bad user address

        mov     ax,ds:[pcb_flags]
        and     ax,mask PF_VERBOSE      ; (ax) = verbose flag
        CALLFAR SuWord                  ; SuWord (ax=word bx:di=dst)
        jc      pdx                     ; Bad user address

        mov     si,pcb_cticIdle         ; (ds:si) = start of scalars
        mov     cx,N_PCB_SCALARS*4      ; (cx) = number of bytes of scalar data
        jmp     short pd30

;*  Segment buffer

pd10:   test    ds:[pcb_flags],mask PF_VERBOSE; detail kernel tics?
        jnz     pd20                    ;  YES, no adjustment required
        add     dx,ax                   ; adjust to first non-kernel seg. num.

pd20:   mov     ax,ds:[pcb_cntSels]     ; (ax) = number of segments
        cmp     dx,ax                   ; Buffer number within range?
        jb      pd25                    ; use 16 segment

        mov     cx,pd_pName.Segmt
        mov     si,pd_pName.Offst       ; cx:si -> name return buffer
        call    flatDump                ; dump flat data
        jmp     short pdx               ; done

pd25:   mov     si,dx                   ; (si) = buffer number
        add     si,ax                   ; (si) = index of word with data sel
        shl     si,1                    ; (si) = offset of word with sel
        mov     ax,ds:[(size pcb_s)+si] ; (ax) = data selector
        mov     ds,ax                   ; (ds) = data segment
        xor     si,si                   ; (ds:si) start of data

        mov     cx,ds                   ; (cx) = data segment
        LOADSL  cx,cx                   ; (cx) = last offset
        inc     cx                      ; (cx) = size of data segment
        mov     ax,cx                   ; (ax) = size of data segment
        CALLFAR SuWord                  ; SuWord (ax=word bx:di=dst)
        jc      pdx                     ; Bad user address
        xor     ax,ax                   ; (ax) = size of data segment
        CALLFAR SuWord                  ; SuWord (ax=word bx:di=dst)
        jc      pdx                     ; Bad user address


;*  Store data in buffer; First word is length of remaining data
;
;   (bx:di) = user buffer
;   (dx)    = adjusted buffer number

pd30:   mov     pd_bufNum,dx            ; Store modified buffer number

        CALLFAR SuBuff                  ; SuBuff(ds:si=src bx:di=dst cx=len)
    assume  ds:nothing,es:nothing
        jc      pdx                     ; Bad user address

;*  Find module name and file segment number

        mov     bx,pd_bufNum            ; (bx) = data buffer number
        or      bx,bx
        jns     pd40                    ;   Segment buffer

;*  Don't need any more info for scalar segment

        xor     ax,ax                   ; (ax) = success
        jmp     pdx

;*  Segment buffer

pd40:   shl     bx,1                    ; (bx) = offset of code selector
        mov     ds,pd_PCB               ; (ds) = PCB
        mov     ax,ds:[(size pcb_s)+bx] ; (ax) = code selector
        mov     bx,pd_pName.Segmt
        mov     di,pd_pName.Offst
        call    SelToName

pdx:    LeaveProc
        ret

pde:    stc
        mov     ax,ERROR_INVALID_DATA
        jmp     pdx

pde2:   stc
        mov     ax,ERROR_INVALID_FUNCTION
        jmp     pdx

EndProc     ProfDump


;***    flatDump - Dump a flat profile buffer to user
;
;       ENTRY   (AX)    = number of use 16 segments
;               (DS)    = PCB
;               (DX)    = data buffer number
;                           >= AX
;               (BX:DI) = user data buffer
;               (CX:SI) = user name buffer (must be at least 9 bytes long)
;
;       EXIT    (CF) = 0, success
;                   (AX) = 0
;                   (BX) = segment number (1 based)
;                   User data buffer filled in
;                   User name buffer filled in
;                           word 0 contains length of string
;               (CF) = 1, failure
;                   (AX) = error code
;
;       USES    ax,bx,cx,dx,di,si,ds,es,flags
;
;       METHOD
;               check limit and return error if index too big
;               set up pointer to pcbf entry for object
;               save old dump start value
;               calculate size to dump
;               output data bytes remaining including this buffer
;               set cDumpSize to max dump size if too big
;               update dump start in pcbf for next time
;               copy cDumpSize bytes to user buffer
;               if old dump start was 0, copy out name
;               set bx to segment number corresponding to index
;
;       WARNING If the "data buffer" is not large enough, the calling
;               process will be terminated.  The caller should provide
;               a 64K segment; this is sufficient because the linker
;               only makes 64K-16 byte code segments due to 286 bug, and we
;               only need size of code segment + 4 bytes.

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   flatDump
        LocalVar    fd_pUserBuf,DWORD   ; far ptr to data return buffer
        LocalVar    fd_pName,DWORD      ; far ptr to name return buffer
        LocalVar    fd_cbStartDump,DWORD ; flag for outputting name

        EnterProc   <bx,di,cx,si>

; BUGBUG 19-Jun-1989 wayneca Must valid user pointers!

;*  Check limit and return error if index too big
        sub     dx,ax                   ; dx = index of pcbf
        cmp     dx,ds:[pcb_cpcbf]       ; Valid flat object index?
        jae     fd10                    ;   NO, fail dump

;*  Set up pointer to pcbf entry for object
        mov     ax,dx                   ; (ax) = index
        mov     dx,size pcbf_s          ; (dx) = size of apcbf entry
        mul     dx                      ; (ax) = offset into apcbf array
        add     ax,pcb_apcbf            ; (ds:ax) -> pcbf for object
        mov     bx,ax                   ; ds:bx -> pcbf

        cmp     ds:[bx].pcbf_fKernel,0  ; KERNEL flat object?
        je      fd15                    ;   NO, go dump

        test    ds:[pcb_flags],mask PF_VERBOSE; Did we collect data on kernel?
        jnz     fd15                    ;   YES, go dump

;BUGBUG 14-Jul-1989 bens Need to handle !PF_VERBOSE flat dumping
;
;   If we add code to support non-kernel flat objects, we'll need to
;   map the "data buffer number" to a pcbf index such that we skip
;   the kernel flat objects (just as we do with pcb_cntSels and
;   pcb_cntKSels).  At present, we don't have those, so we know there
;   are no more buffers to dump when we get to the flat objects (if
;   PF_VERBOSE is not set).
;
;*  Fail dump request

fd10:   stc                             ;   NO, indicate done with dumping
        mov     ax,ERROR_INVALID_DATA   ; ax = no more data to dump
        jmp     fdx                     ; go exit

;*  Compute how much data is left to dump

fd15:   mov     eax,ds:[bx].pcbf_cbDump ; eax = count of bytes dumped so far
        mov     ecx,[bx].pcbf_cb        ; ecx = total object size
        sub     ecx,eax                 ; ecx = size left to dump

;*  Store bytes remaining (including the chunk we are about to transfer)
        push    eax                     ; Save pcbf_cbDump
        SaveReg <ds,bx,ecx>
        mov     [bx].pcbf_cbDump,ecx    ; use pcbf_cbDump for SuBuff
        lea     si,ds:[bx].pcbf_cbDump  ; (ds:si) -> bytes left

        mov     bx,fd_pUserBuf.Segmt
        mov     di,fd_pUserBuf.Offst    ; (bx:di) -> user buffer
        mov     cx,4                    ; (cx) = length of output
        CALLFAR SuBuff                  ; SuBuff(ds:si=src bx:di=dst cx=len)
        RestoreReg <ecx,bx,ds>
        pop     ds:[bx].pcbf_cbDump     ; Restore original pcbf_cbDump
        jc      fdx                     ; Bad user address

;*  Only can dump MAX_DUMP at once, though
        cmp     ecx,MAX_DUMP            ; Small enough to dump all?
        jb      fd20                    ;   YES, go dump

        mov     ecx,MAX_DUMP            ;   NO, reduce to max

;*  Check buffer size
fd20:   movzx   eax,fd_pUserBuf.Segmt
        LOADSL  eax,eax                 ; determine segment length
        stc                             ; prepare for error return
        jnz     fdx                     ; exit if failed to get length

        cmp     eax,ecx                 ; User segment large enough?
        jb      fdx                     ;   NO, go exit

;*  Update dump start in pcbf for next time
        mov     eax,ds:[bx].pcbf_cbDump ; eax = old start offset
        mov     edx,eax                 ; edx = offset for dumping
        add     eax,ecx                 ; eax = new start offset
        cmp     eax,ds:[bx].pcbf_cb     ; All dumped?
        jne     fd30                    ;   NO, go update count

        xor     eax,eax                 ;   YES, reset dump count

fd30:   mov     ds:[bx].pcbf_cbDump,eax ; Update count of bytes dumped


;*  Copy bytes to user buffer
;
;   ds -> pcb
;   ds:bx -> pcbf
;   edx = offset in range for this object to dump
;   ecx = count of bytes to transfer


        SaveReg <ds>
        mov     esi,ds:[pcb_pObjData]   ; esi -> start of count allocation
        add     esi,ds:[bx].pcbf_offDmp ; esi -> start of count object
        add     esi,edx                 ; esi -> start of chunk to dump
        movzx   edi,di                  ; edi = user buffer start

; BUGBUG 19-Jun-1989 wayneca NEED TO USE SUBUFF
        mov     ax,fd_pUserBuf.Segmt
        mov     es,ax                   ; (es:edi) -> user buffer
    assume  es:nothing
        FlatContext <ds>,eax            ; (ds:esi) -> data for buffer
        shr     ecx,2                   ; ecx = count of dwords to move
        rep movs dword ptr es:[edi], dword ptr ds:[esi]
        RestoreReg <ds>

;*  Store name and file object number

        SaveReg <bx,dx,si,ds>
        push    cs
        pop     ds
    assume  ds:nothing
        mov     si,offset cs:dosModName ; (si) -> dos module name
        mov     cl,ds:[si]              ; (cl) = module name length
        mov     ch,0                    ; (cx) = module name length
        mov     bx,fd_pName.Segmt       ; (bx) = user name buffer segment
        mov     di,fd_pName.Offst       ; bx:di -> user buffer
        inc     si                      ; (ds:si) -> module name
        CALLFAR SuBuff                  ; SuBuff(ds:si=src bx:di=dst cx=len)
        RestoreReg <ds,si,dx,bx>
    assume  es:nothing
        jc      fdx                     ; Bad user address

;*  di -> end of name; add trailing 0

        mov     al,0                    ; (al) = null terminator for ASCIIZ
        SaveReg <bx,dx,ds>
        mov     bx,fd_pName.Segmt       ; (bx) = user name buffer segment
        CALLFAR SuByte                  ; SuByte(al=byte bx:di=dst)
    assume  es:nothing
        RestoreReg <ds,dx,bx>
        jc      fdx                     ; Bad user address

;*  return object number
;
;   ds -> pcb
;   ds:bx -> pcbf

fd50:   mov     bx,[bx].pcbf_ord        ; (bx) = object number
        xor     ax,ax                   ; CLC

fdx:    LeaveProc
        ret

EndProc     flatDump


;***    SelToName - Map selector to Module Name and Segment Number
;
;       ENTRY   (AX) = selector
;               (BX:DI) = buffer to receive name (must have room for 9 bytes)
;
;       EXIT    (CF) = 0, found selector
;                   (BX) = file segment number
;                   Buffer filed in with ASCIIZ module name
;               (CF) = 1, selector not found
;               (AX) = Error
;
;       USES    ax,bx,cx,dx,di,si,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   SelToName,hybrid

        EnterProc
        push    bx                      ; save selector of user buffer
        mov     bx,ax
        test    bx,TABLE_MASK
        jnz     sd1                     ; LDT selector
;*  Map GDT selector to name,segment number
; BUGBUG 20-Jun-1989 wayneca Assumes DS points to the PCB -
;                               breaks the call from Device drivers

        call    GDTSelToName    ; ds:si=name bx=segnum = GDTSelToName(bx=sel)
        jmp     short sd2

sd1:    call    LDTSelToName    ; ds:si=name bx=segnum = LDTSelToName(bx=sel)
        jnc     sd2                     ; Found selector in MTEs

        push    cs                      ; Unknown LDT selector
        pop     ds
    assume  ds:nothing
        mov     si,offset cs:unkLdtName ; (cs:si) -> unknown name

sd2:    mov     dx,bx                   ; Save file segment number
        pop     bx                      ; (bx) = selector of user name buffer

;* bx:di -> user name buffer

        mov     cl,ds:[si]              ; (cl) = module name length
        mov     ch,0                    ; (cx) = module name length

        inc     si                      ; (ds:si) -> module name
        SaveReg <bx,dx,ds>
        CALLFAR SuBuff                  ; SuBuff(ds:si=src bx:di=dst cx=len)
        RestoreReg <ds,dx,bx>           ; di updated
    assume  es:nothing
        jc      sde                     ; Bad user address

        mov     al,0                    ; (al) = null terminator for ASCIIZ
        SaveReg <dx,ds>
        CALLFAR SuByte                  ; SuByte(al=byte bx:di=dst)
    assume  es:nothing
        RestoreReg <ds,dx>
        jc      sdx                     ; Bad user address

        mov     bx,dx                   ; (bx) = file segment number
        xor     ax,ax                   ; (ax) = success

sdx:    LeaveProc
        ret

sde:    stc
        mov     ax,ERROR_INVALID_DATA
        jmp     sdx

EndProc SelToName


;***    GDTSelToName - Map GDT selector to Module Name and Segment Number
;
;       ENTRY   (BX) = GDT selector
;               (ds) = PCB
;
;       EXIT    (CF) = 0, found selector
;                   (BX)    = segment number (1 based)
;                   (DS:SI) = ptr to module name (first byte is length)
;               (DS) = (CS)
;
;       USES    ax,si,ds,flags

dosModName  db  8,"OS2KRNLD"    ; Length, name
unkGdtName  db  8,"GDT_UNKN"    ; Length, name

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   GDTSelToName

        SaveReg <di,es>
        push    ds
        pop     es                      ; es => PCB for rep scasw
    assume  es:nothing

        mov     cx,ds:[pcb_cntKSels]    ; (cx) = length of K code sel list
        mov     dx,cx                   ; (dx) = length of K code sel list
        mov     ax,bx                   ; (ax) = CS of interrupted code
        and     ax,RPL_CLR              ; (ax) = CS with RPL=0
        mov     di,size pcb_s           ; (es:di) = code sel list
        repne scasw                     ; search for selector
        je     gtn20                    ; Found match

        mov     si,offset cs:unkGdtName ; (si) -> unknown name
        stc
        jmp     short gtnx

gtn20:  mov     si,offset cs:dosModName ; (si) -> dos module name
        sub     dx,cx                   ; (dx) = index to selector + 1
        dec     dx                      ; (dx) = index to selector
        mov     bx,dx
        add     bx,dx
        add     bx,pcb_aordKSel
        mov     bx,[bx]                 ; (bx) = segment number
        clc

gtnx:   push    cs
        pop     ds
        RestoreReg <es,di>
        ret

EndProc     GDTSelToName


;***    LDTSelToName - Map LDT selector to Module Name and Segment Number
;
;       ENTRY   (BX) = LDT selector
;               CLD
;
;       EXIT    (CF) = 0, found selector
;                   (BX)    = file segment number
;                   (DS:SI) = ptr to module name (first byte is length)
;               (CF) = 1, selector not found
;                   (BX) = same as at entry
;
;       USES    ax,bx,cx,dx,si,ds,es,flags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   LDTSelToName
        SaveReg <di>
        call    MTEFindSel              ; search for selector
        jc      ltn1                    ;   ERROR, selector not found

;* bx = file segment number
;  ds = flat
;  esi -> MTE

;*  Copy module name to 16:16 buffer to make it easy for SuBuff

        DosContext <es>,ax
        mov     edi,offset es:[achMod]  ; es:edi -> 16:16 buffer
        push    di                      ; save start
        mov     esi,[esi].mte_swapmte   ; esi = pointer to swappable mte
        mov     esi,ds:[esi].smte_restab; ds:esi -> module name (1st byte=len)
        movzx   ecx,byte ptr ds:[esi]   ; ecx = len - 1
        inc     ecx                     ; ecx = length to copy
        rep movs byte ptr es:[edi], byte ptr ds:[esi] ; Copy <len,name>
        pop     si                      ; si -> 16:16 buffer

        push    es
        pop     ds                      ; ds:si -> 16:16 buffer
    assume  ds:nothing

ltn1:   RestoreReg <di>
        ret
EndProc     LDTSelToName


;***LP  MTEFindSel - map selector to <name,segment number>
;
;       ENTRY:  bx = selector
;
;       EXIT:   (CF) = 0, found selector
;                   bx = file segment number
;                   ds:esi -> MTE
;                   ax = segment size
;               (CF) = 1, did not find selector
;
;       USES:   eax,ebx,ecx,edx,edi,esi,ds,eflags

    assume  ds:nothing,es:nothing,ss:TASKAREA
Procedure   MTEFindSel

        and     bx,RPL_CLR              ; clear ring bits

        FlatContext <ds>,ax
        mov     dx,ss:ptda_module       ; dx = hmte for this program
        call    MTEHandleToPtr          ; esi = pmte
        jc      smlie1                  ;   invalid HMTE

;*  ds:esi -> MTE

        call    findSel                 ; Selector in MTE?
        jnc     short smlx              ;   YES, return info

;*  Need to scan entire MTE list (DLLs only!)

        mov     esi,ds:_mte_h           ; esi = head of MTE list

sml10:  or      esi,esi                 ; end of module list ?
        jz      short sml30             ;   YES, go exit

        test    [esi].mte_flags1,LIBRARYMOD ; DLL?
        jz      short sml20             ;   NO, do not scan other apps

        call    findSel                 ; Selector in MTE?
        jnc     short smlx              ;   YES, return info

sml20:  mov     esi,[esi].mte_link      ;   NO, esi = next MTE
        jmp     sml10                   ; Continue searching

sml30:  stc

smlx:   ret

smlie1: InternalError <MTEFindSel: invalid ptda_module>

EndProc     MTEFindSel


;***LP  findSel - find selector in MTE
;
;       ENTRY   esi -> MTE to be searched
;               bx = 16-bit selector, with RPL bits zeroed
;               ds = FLAT
;
;       EXIT    CF=0, found selector
;                   bx = file segment index (1-based)
;                   ax = size of segment
;                   esi -> MTE
;               CF=1, selector not found
;
;       USES    eax,ebx,ecx,edi,eflags

    assume  ds:FLAT,es:nothing,ss:TASKAREA
Procedure   findSel

        mov     eax,[esi].mte_swapmte   ; eax = pointer to swappable mte
        mov     edi,ds:[eax].smte_objtab; edi = seg table start
        mov     ecx,ds:[eax].smte_objcnt; ecx = object count
        jecxz   short fs15              ; No OTE or STEs (kernel MVDM MTE!)


        mov     dx,word ptr ds:[esi].mte_flags2      ; 16-bit STEs?
        and     dx,MTEFORMATMASK
        cmp     dx,MTEFORMATNE

        mov     dx,1                    ; dx = ste index
        jne     fs40                    ;   NO, go handle 32-bit OTEs

;*  Entry
;       edi -> STE
;       ecx = count of objects still to process
;       bx = selector to locate
;       dx = segment count

fs10:   mov     ax,[edi].ste_selector   ; ax = STE selector
        and     ax,RPL_CLR              ; ax = RPL bits cleared
        cmp     ax,bx                   ; Selector match?
        je      fs20                    ;   Yes, go exit (CLC)

        inc     dx                      ; dx = next STE index
        add     edi,size ldrste_s       ; edi = next STE
        loop    fs10                    ; Continue search

fs15:   stc                             ; Search failed
        ret

fs20:   mov     bx,dx                   ; bx = STE index
        mov     ax,[edi].ste_size       ; ax = segment size
        ret

;*  Scan OTEs for 16-bit object
;
;   Entry
;       edi -> OTE
;       ecx = count of objects still to process
;       bx = selector to locate
;       dx = object count

;*  Map sel to laddr using CRMA

fs40:   movzx   eax,bx                  ; eax = selector
        shr     eax,3                   ; get rid of table and RPL bits
        shl     eax,16                  ; eax = linear base address

;*  eax = base address to match

fs50:   cmp     eax,ds:[edi].ote_base   ; Found object?
        je      fs60                    ;   Yes

        add     edi,size ldrote_s       ; advance OTE pointer
        inc     edx                     ; advance object ordinal
        loop    fs50                    ; do next object

;*  Did not find object

        stc
        ret                             ; go exit

fs60:   mov     eax,ds:[edi].ote_size   ; eax = object size (ax is significant)
        jmp     short fs20              ; go exit


EndProc     findSel


;***LP  MTEHandleToPtr - map HMTE to PMTE
;
;       ENTRY   dx = HMTE
;               ds = FLATCONTEXT
;
;       EXIT    (CF) = 0, found MTE
;                   ds:esi = PMTE
;               (CF) = 1, did not find MTE
;
;       USES    esi,ds,eflags

Procedure   MTEHandleToPtr

        mov     esi,ds:_mte_h           ; esi = head of MTE list

htp10:  or      esi,esi                 ; end of module list ?
        jz      short htp20             ;   YES, go exit

        cmp     dx,ds:[esi].mte_handle  ; Found MTE?
        jz      short htp30             ;   Yes, go exit

        mov     esi,[esi].mte_link      ; esi = next MTE
        jmp     htp10                   ; Continue searching

htp20:  stc                             ; handle not an mte handle

htp30:  ret

EndProc     MTEHandleToPtr




IFDEF UNUSED
;**     GetTimer0 - Get a Word from Timer 0
;
;       ENTRY   none
;
;       EXIT    ax = word received from timer
;
;       USES    ax

Procedure GetTimer0,near

        push    cx

 cmd = binary data + square wave + r/w high and low byte + channel 0
        mov     al,TC_DA_BIN + TC_MD_SQU + TC_OP_HILO + TC_CH_0
        out     TP_CMD,al       ; send command to timer chip
        kIODelay cx
        in      al,TP_CH0       ; (al) = low byte from channel 0
        xchg    al,ah           ; (ah) = low byte from channel 0
        kIODelay cx
        in      al,TP_CH0       ; (al) = high byte from channel 0
        xchg    al,ah           ; (ax) = word from channel 0

        pop     cx
        ret

EndProc GetTimer0
ENDIF   ; UNUSED


;***    SetTimer0 - set timer 0's frequency
;
;       Set timer 0 to produce a square wave at 1193180/divisor Hz.
;       If divisor = 0 it is equivalent to 65536.
;
;       ENTRY   bx = divisor
;
;       EXIT    None
;
;       USES    ax

Procedure SetTimer0,near

; cmd = binary data + square wave + r/w high and low byte + channel 0
        mov     al,TC_DA_BIN + TC_MD_SQU + TC_OP_HILO + TC_CH_0
;; BUGBUG: old code: see mikem:
        out     TP_CMD,al               ; send cmd to command register
        kIODelay ax
        mov     al,bl                   ; (al) = lo byte of divisor
;; BUGBUG: old code: see mikem:
        out     TP_CH0,al               ; send low byte to channel 0
        kIODelay ax
        mov     al,bh                   ; (al) = hi byte of divisor
;; BUGBUG: old code: see mikem:
        out     TP_CH0,al               ; send hi byte to channel 0
        ret

EndProc SetTimer0

HIGH3CODE ENDS

ENDIF; PROFILE

        END
