/*static char *SCCSID = "@(#)dyndto.c	6.3 91/10/17";*/
#define SCCSID  "@(#)dyndto.c	6.3 91/10/17"
/*
 *      This file implements routines to manage the DynamicTrace
 *      DTO structures (Dynamic Trace Objects).
 *
 *      A DTO describes the current state of tracing for a module.
 *      It is made up of a kernel header information (dto_dti, or the
 *      Dynamic Trace Info) and client information (dto_dth, or the
 *      Dynamic Trace Header).
 *
 *      The client information also includes an array of DWORDS
 *      (Dynamic Trace Variables array) and a contiguous list of
 *      DTRs (Dynamic Trace Records).  Each DTR provides information
 *      on a per-tracepoint basis.  The code to manage the DTRs is
 *      found in dyndtr.c and dyndtra.asm.
 *
 *      See dynci.asm for the Dynamic Trace Command Interpreter.
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 */

#define INCL_TYPES                      // for basedef.h typedefs
#define INCL_OS2STD                     // 7-29 NR not sure if needed so put in

#include <basemac.h>
#include <basedef.h>
#define FNO_NUM FNO_DYNDTO_C
#include <fnotask.h>
#include <osmac.h>
#include <osconst.h>
#include <ostypes.h>
#include <infoseg.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <mi.h>
#include <pgexport.h>
#include <vmexport.h>
#include <selxport.h>
#include <seldesc.h>
#include <tkexport.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <screen.h>
#include <sem.h>
#include <tkptda.h>
#include <ldrxport.h>
#include <error.h>
#include <error2.h>
#include <vmowner.h>
#include <dynxport.h>
#include <dynapi.h>
#include <dynlocal.h>
#include <string.h>

#pragma code_seg("DYNSWAP_TEXT", "CODE")

#if     RAS

/***LP  dynDTOLink - Link a DTO into lists.
 *
 *      dynDTOLink places a DTO onto the pdtoHeadAll and pdtoHeadScan lists.
 *
 *      The loader semaphore must be set here.
 *
 *      ulong_t dynDTOLink (dto_t *pdto);
 *
 *      ENTRY   pdto    - DTO pointer
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOLink      (pdto)
dto_t   *pdto;
{
        TKMayBlock();

        // Note that the tracepoint object is listed in pdtoHeadAll and
        // pdtoHeadScan, then simply link them in at the front.

        pdto->dto_dti.dti_lfState |= (DTO_LALL | DTO_LSCAN);
        pdto->dto_dti.dti_pDTONext = pdtoHeadAll;
        pdto->dto_dti.dti_pDTOScan = pdtoHeadScan;
        pdtoHeadAll = pdtoHeadScan = pdto;
        return (NO_ERROR);
}


/***LP  dynDTOUnLink - Unlink a DTO from lists.
 *
 *      dynDTOUnLink unlinks the DTO from all of the lists it may be on.
 *
 *      ulong_t dynDTOUnLink (dto_t *pdto);
 *
 *      ENTRY   pdto    - DTO pointer
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOUnLink    (pdto)
dto_t   *pdto;
{
        ulong_t dpdtoNext;              // DELTA pointer to get to
                                        // link field from pdto.
        register dto_t  **ppdtoPrev;    // Pointer to link field
        register dto_t  *pdtoCur;       // DTO search pointer

        TKMayBlock();

        // If DTO is detached, decrement cdtoDetached.

        if (pdto->dto_dti.dti_lfState & DTO_DETACHED) {
                --cdtoDetached;
                dynAssert (cdtoDetached != (ulong_t) -1);
        }

        // If DTO in process of being loaded, decrement cdtoLoading.

        if (pdto->dto_dti.dti_lfState & DTO_LOADING) {
                --cdtoLoading;
                dynAssert (cdtoLoading != (ulong_t) -1);
        }

        // While on some list, remove the DTO from that list.

        while (pdto->dto_dti.dti_lfState & (DTO_LALL | DTO_LSCAN)) {

                //      Select the list to remove the DTO from.

                if (pdto->dto_dti.dti_lfState & DTO_LALL) {
                        pdto->dto_dti.dti_lfState &= ~DTO_LALL;
                        ppdtoPrev = & pdtoHeadAll;
                        dpdtoNext = FIELDOFFSET (dto_t, dto_dti) +
                                    FIELDOFFSET (dti_t, dti_pDTONext);
                }
                else if (pdto->dto_dti.dti_lfState & DTO_LSCAN) {
                        pdto->dto_dti.dti_lfState &= ~DTO_LSCAN;
                        ppdtoPrev = & pdtoHeadScan;
                        dpdtoNext = FIELDOFFSET (dto_t, dto_dti) +
                                    FIELDOFFSET (dti_t, dti_pDTOScan);
                }
                else {
                        panic ("dynDTOUnLink");         // Coding Error.
                }

                // Unlink the dto from the selected list.  It must
                // be on the selected list.

                pdtoCur = *ppdtoPrev;
                while ((pdtoCur != NULL) && (pdtoCur != pdto)) {
                        ppdtoPrev = (dto_t **) ((ulong_t) pdtoCur + dpdtoNext);
                        pdtoCur = *ppdtoPrev;
                }
                dynAssert(pdtoCur != NULL);
                dynAssert(ppdtoPrev != NULL);
                *ppdtoPrev = *(dto_t **) ((ulong_t) pdtoCur + dpdtoNext);
        }

        return (NO_ERROR);
}


/***LP  dynDTORead11 - Read a DTO from user space (Level 1 format only)
 *
 *      dynDTORead11 reads a 1.x (16-bit) request packet into a DTO,
 *      and returns a pointer to the newly created DTO.
 *
 *      dynDTORead11 must interpret some of the data it reads, to get
 *      the length of the request packet, and to read in the records.
 *      The size of the request packet is not restricted except by
 *      available resident memory, and the 16-bit data structure.
 *
 *      Use of the 16-bit version is not recommended, as the 32-bit
 *      version handles all cases, and tracepoints can be read and
 *      inserted faster.
 *
 *      dynDTORead11 is not a performance-critical routine.  It need
 *      only be correct - not fast.
 *
 *      The loader semaphore need not be set here.
 *
 *      ulong_t dynDTORead11    (ppdto, pdthUser)
 *
 *      ENTRY   ppdto           : Place to return address of new dto
 *              pdthUser        : USER SPACE address of request packet
 *
 *      EXIT    ulong_t, standard error code
 *
 *      Pseudocode :
 *
 */

ulong_t dynDTORead11    (ppdto, pdthUser)
dto_t   **ppdto;
dth_t   *pdthUser;
{
        ulong_t rc;
        VMAC    vmac;
        ulong_t i;

        rps_t   *prpsUser;      // 16-bit version's header address
        rps_t   rpsTemp;        // 16-bit version's header
        rpr_t   *prprUser;      // 16-bit version's per-record header address
        rpr_t   rprTemp;        // 16-bit version's per-record header

        ulong_t cbdto;          // DTO size.
        dto_t   *pdto;          // DTO pointer
        dtr_t   *pdtr;          // DTR pointer
        dtr_t   *pdtrEnd;       // Pointer to end of DTO

        TKMayBlock();

        // Read the header onto the stack.

//      prpsUser = 16:16 address of (pdthUser...?);

        prpsUser = (rps_t *) pdthUser;

        if (NO_ERROR != (rc = TKFuBuff ((char *) SSToDS(&rpsTemp),
            (char *) prpsUser, sizeof(rps_t), TK_UM_FATAL)))
                return (rc);

        // Verify the minimum request packet size.

        if (rpsTemp.rps_length <= sizeof (rps_t))
                return (ERROR_INVALID_DATA);

        // Find the required size of the (32-bit) DTO:
        // Account for:
        //      1) Add dto_dti size (internals, hash table, etc)
        //      2) Convert rps_s to dth_s (a part of the dto)
        //      3) Convert rpr_s to dtr_s, for each record.

        cbdto = rpsTemp.rps_length - sizeof (rps_t) + sizeof(dto_t) +
                rpsTemp.rps_recordcnt * (sizeof (dtr_t) - sizeof (rpr_t));

        if (NO_ERROR != (rc = VMAllocMem(cbdto,         // cb = cbdto
                        cbdto,                          // cbCommit = all
                        VMAC_ARENASYS |                 // System Arena
                        VMAC_LOCANY |                   // Any location
                        VMAC_ALIGNPAGE |                // Page aligned
                        PG_W |                          // Writable pages
                        PG_RESIDENT,                    // Resident pages
                        HOBNULL,                        // no hobptda
                        DYNDTOOWNER,                    // hobowner
                        HOBNULL,                        // no hobmte
                        0,                              // no flselmgr
                        0,                              // no blkno
                        SSToDS (&vmac)))) {             // pac
                return (rc);
        }

        pdto = *ppdto = (dto_t *) vmac.ac_va;

        // Zero initialize the dti structure in the dto.
        // This includes the hash table.

        memset ((void *) &(pdto-> dto_dti), 0, sizeof(dti_t));

        // Initialize nonzero dto_dti variables.

        pdto->dto_dti.dti_hDTO = vmac.ac_hob;
        pdto->dto_dti.dti_cbDTO = cbdto;
        pdto->dto_dti.dti_lAddrMin = -1;

        // Initialize all dto_dth variables from rpsTemp.
        //
        // Note: rpsTemp.rps_length is included in the calculation to
        // get cbdto.  So, (cbdto - sizeof(dti_t)) gives us dth_cbDT.
        //
        // Note: rpsTemp.rps_majorevent must be added to each DTR record
        // as it is read in.

        pdto->dto_dth.dth_Level    = (ulong_t)  rpsTemp.rps_level;
        pdto->dto_dth.dth_flDT     = (ushort_t) 0;
        pdto->dto_dth.dth_cbDT     = (ulong_t)  cbdto - sizeof (dti_t);
        pdto->dto_dth.dth_hMTE     = (ulong_t)  rpsTemp.rps_handle;
//      pdto->dto_dth.dth_ID       = (ushort_t) rpsTemp.rps_majorevent;
        pdto->dto_dth.dth_cDTR     = (ulong_t)  rpsTemp.rps_recordcnt;
        pdto->dto_dth.dth_cbLogMax = (ulong_t)  rpsTemp.rps_bufflen;
        pdto->dto_dth.dth_clVar    = (ulong_t)  0;

        // Read in each record.  For each record, we read it's header
        // onto the stack, fill in it's dtr in-place, then read in its
        // opcode stream.  If we ever get an error, we break out of
        // the loop with rc set.  We must also continuously watch out
        // for writing past the end of the dto.

        prprUser = (rpr_t *) (prpsUser+1);
        pdtr = (dtr_t *) (pdto+1);
        pdtrEnd = (dtr_t *) ((char *) pdto + cbdto);

        for (i=0; i < pdto->dto_dth.dth_cDTR; i++) {

                // read next rpr header into rprTemp.

                if (NO_ERROR != (rc = TKFuBuff ((char *) SSToDS(&rprTemp),
                    (char *) prprUser, sizeof(rpr_t), TK_UM_FATAL))) {
                        break;
                }

                // See if we are ging to write over the end of the DTO.

                if ((ulong_t) (pdtr + 1) + (ulong_t) rprTemp.rpr_cmdlen >
                    (ulong_t) pdtrEnd) {
                        rc = ERROR_INVALID_DATA;
                        break;
                }

                // We will not overrun the DTO.
                // Fill in the DTR header in-place, from rprTemp.

                pdtr-> dtr_Object   = (ulong_t)  rprTemp.rpr_segment;
                pdtr-> dtr_Offset   = (ulong_t)  rprTemp.rpr_offset;
                pdtr-> dtr_MinCode  = (ushort_t) rprTemp.rpr_minorevent;
                pdtr-> dtr_MajCode  = (ushort_t) rpsTemp.rps_majorevent;
                pdtr-> dtr_cbDTCODE = (ushort_t) rprTemp.rpr_cmdlen;
                pdtr-> dtr_Opcode   = (uchar_t)  rprTemp.rpr_instr;
                pdtr-> dtr_Reserved = (uchar_t)  0;

                // move pdtr and prprUser to the command list section.

                pdtr++;
                prprUser++;

                // If there are no instructions to read, just continue.

                if (rprTemp.rpr_cmdlen == 0)
                        continue;

                // Read in the instructions.

                if (NO_ERROR != (rc = TKFuBuff ((char *)pdtr, (char *) prprUser,
                    (int) rprTemp.rpr_cmdlen, TK_UM_FATAL))) {
                        break;
                }

                // move pdtr and prprUser to the next record.

                pdtr = (dtr_t *) ((ulong_t) pdtr +
                                  (ulong_t) rprTemp.rpr_cmdlen);

                prprUser = (rpr_t *) ((ulong_t) prprUser +
                                      (ulong_t) rprTemp.rpr_cmdlen);
        }

        // Verify that the entire request packet was read in perfectly.

        if ((rc == NO_ERROR) && (pdtr != pdtrEnd)) {
                rc = ERROR_INVALID_DATA;
        }

        if (rc != NO_ERROR) {

                // Some Read failed - free the dto and leave.
                // The free must succeed.

                ulong_t rcTemp;
                do {
                        rcTemp = VMFreeMem((ulong_t) *ppdto, HOBNULL, NA);
                } while (rcTemp == ERROR_INTERRUPT);

                dynAssert (rcTemp == NO_ERROR);
        }

        return (rc);
}


/***LP  dynDTORead20 - Read a DTO from user space (Level 2 format only)
 *
 *      dynDTORead20 reads a version 2.0 request packet into a DTO,
 *      and returns a pointer to the newly created DTO.
 *
 *      dynDTORead20 does not interpret the data it reads, beyond getting
 *      the length of the request packet.  The size of the request packet
 *      is not restricted except by available resident memory.
 *
 *      The loader semaphore need not be set here.
 *
 *      ulong_t dynDTORead20 (ppdto, pdthUser)
 *
 *      ENTRY   ppdto           : Place to return address of new dto
 *              pdthUser        : USER SPACE address of request packet
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTORead20 (ppdto, pdthUser)
dto_t   **ppdto;
dth_t   *pdthUser;
{
        ulong_t rc;
        ulong_t cbdto;
        VMAC    vmac;

        TKMayBlock();

        if (NO_ERROR != (rc = TKFuDWord (SSToDS(&cbdto),
                                         &(pdthUser-> dth_cbDT))))
                return (rc);

        // Verify minimim request packet size for sanity.

        if (cbdto <= sizeof (dth_t))
                return (ERROR_INVALID_DATA);

        // Account for the dti structure at start of dto_s.
        // These internal variables are invisible to the user.

        cbdto += sizeof(dti_t);

        if (NO_ERROR != (rc = VMAllocMem(cbdto,         // cb = cbdto
                        cbdto,                          // cbCommit = all
                        VMAC_ARENASYS |                 // System Arena
                        VMAC_LOCANY |                   // Any location
                        VMAC_ALIGNPAGE |                // Page aligned
                        PG_W |                          // Writable pages
                        PG_RESIDENT,                    // Resident pages
                        HOBNULL,                        // no hobptda
                        DYNDTOOWNER,                    // hobowner
                        HOBNULL,                        // no hobmte
                        0,                              // no flselmgr
                        0,                              // no blkno
                        SSToDS (&vmac)))) {             // pac
                return (rc);
        }

        *ppdto = (dto_t *) vmac.ac_va;

        // Zero initialize the dti structure in the dto.
        // This includes the hash table.

        memset ((void *) &((*ppdto)-> dto_dti), 0, sizeof(dti_t));

        // Initialize nonzero dto_dti variables.

        (*ppdto)->dto_dti.dti_hDTO = vmac.ac_hob;
        (*ppdto)->dto_dti.dti_cbDTO = cbdto;
        (*ppdto)->dto_dti.dti_lAddrMin = -1;

        //      Read in the entire request packet, starting at dto_dth.
        //      TKFuBuff TCYields as needed.

        if (NO_ERROR != (rc = TKFuBuff ((char *) &(*ppdto)->dto_dth,
                                        (char *) pdthUser,
                                        (int) cbdto - sizeof(dti_t),
                                        TK_UM_FATAL))) {

                // Read failed - free the dto and leave.
                // The free must succeed.

                ulong_t rcTemp;
                do {
                        rcTemp = VMFreeMem((ulong_t) *ppdto, HOBNULL, NA);
                } while (rcTemp == ERROR_INTERRUPT);

                dynAssert (rcTemp == NO_ERROR);
        }

        return (rc);
}


/***LP  dynDTOVerify - Verify a user's request packet just after reading it.
 *
 *      dynDTOVerify verifies the user's request packet format.
 *
 *      dynDTOVerify assumes that the allocated size of the dto is
 *      sizeof (dti_t) + pdto->dto_dth.dth_cbDT.
 *
 *      dth_clVar is verified implicitly by verifying the DTRs.
 *
 *      ulong_t dynDTOVerify  (dto_t *pdto, dtlv_t *pdtlv);
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 *      Pseudocode :
 *
 *      Verify the DTR chain in the DTO for accuracy.
 *
 *      Verify count of DTRs
 *
 *      Verify overall DTO size
 *
 */

ulong_t dynDTOVerify  (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        dtr_t   *pdtrCur;
        dtr_t   *pdtrMax;               // Maximum DTR address
        ulong_t cdtr = 0;               // DTR count

        TKMayBlock();

        // pdtr = Address of first DTR in the DTO.

        pdtrCur = (dtr_t *) ((ulong_t) (pdto + 1) + 4*pdto->dto_dth.dth_clVar);

        // pdtrMax = Address just beyond last DTR in the DTO.

        pdtrMax = (dtr_t *) ((ulong_t) pdto + sizeof (dti_t) +
                  pdto->dto_dth.dth_cbDT);

        while (pdtrCur < pdtrMax) {

                // Verify that we can access the whole dtr header before
                // attempting to access it.  We verify the DTR header here.
                // The major code in the DTR is currently restricted to
                // 255 or less, because of the STDA format.

                if ((pdtrCur+1 > pdtrMax) ||
                    (pdtrCur-> dtr_MajCode > 255))
                        return (ERROR_INVALID_DATA);

                // Move to the next dtr.

                if ((pdtrCur = pdtrNextSeq(pdtrCur)) > pdtrMax)
                        return (ERROR_INVALID_DATA);

                // Note: an address is a long, and dtr_cbDTCODE is a short,
                // and we never give out addresses in the last 64K, so we
                // have no problem with wraps.

                cdtr++;
        }

        // Verify that dth_cDTR and dth_cbDT are corect.

        if ((cdtr == 0) ||
           (pdto-> dto_dth.dth_cDTR != cdtr) ||
           (pdtrCur != pdtrMax))
                return (ERROR_INVALID_DATA);

        pdto-> dto_dti.dti_lfState |= DTO_TRUSTED;

        return (NO_ERROR);
}


/***LP  dynDTOFixup - Fixup a request packet into a valid DTO
 *
 *      dynDTOFixup performs fixups on all addresses in the DTO.
 *      It creates the hash table, fixes up and compiles the
 *      tracepoints, and sets up dti_aSel.
 *
 *      Note that there is no need for a dynDTOUnFixup call because
 *      the hash table organization and the DynTrc object locator
 *      array handles the motion of tracepoints without scanning the DTRs.
 *
 *      ulong_t dynDTOFixup (dto_t *pdto, dtlv_t *pdtlv);
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 *      Pseudocode :
 *
 *      For (pdtr = each DTR), {
 *              dynDTRFixup (dtr);
 *              fixup dtr_Offset from dtr_Object;
 *              i = hash value;
 *              dtr_Object = dti_apDTRHash[i];
 *              dti_apDTRHash[i] = pdtr;
 *              dtr_Reserved = 0;
 *              dynDTRCompile (pdtr);
 *      }
 *
 *      HashTable is valid.
 *
 */

ulong_t dynDTOFixup (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        ulong_t rc;

        TKMayBlock();

//      Allocate the precompiler's temporary workspace for the fixup run.

        if (NO_ERROR != (rc = VMAllocKHB(VM_HKH_PUB_RESRW, DYN_MAXTARGETS*2,
            DYNDTOTMPOWNER, NA, (char **) &(pdtlv->dtlv_pasTargets)))) {
                return (rc);
        }

//      Fixup all the records.  Note that the hash table is zero here.

        rc = dynDTRScan (pdto,
                        DYN_DTRS_ERRORPROP |
                        DYN_DTRS_ERROREXIT,
                        0,
                        -1,
                        dynDTRFixup,
                        pdtlv);

//      Free the precompiler's temporary workspace.

        VMFreeKHB (VM_HKH_PUB_RESRW, (void *) pdtlv->dtlv_pasTargets);

        if (rc == NO_ERROR) {
                pdto->dto_dti.dti_lfState |= DTO_FIXEDUP;
        }

        return (rc);
}


/***LP  dynDTOSet - Set tracepoints in a module after fixing them up.
 *
 *      dynDTOSet is used to store the tracepoints in a module's code
 *      objects.  The DTO must be completely fixed up and linked onto
 *      the proper search list at this point, as some tracepoints may
 *      be hit while we are still inserting tracepoints.
 *
 *      ulong_t dynDTOSet       (dto_t *pdto, dtlv_t *pdtlv);
 *
 *      The loader semaphore must be set here.
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOSet       (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        ulong_t rc;

        TKMayBlock();

        dynAssert (pdto->dto_dti.dti_lfState & DTO_FIXEDUP);

        pdto->dto_dti.dti_lfState |= DTO_SOMESET;

//      Set all the records.

        rc = dynDTRScan (pdto,
                        DYN_DTRS_ALIAS |
                        DYN_DTRS_ERRORPROP |
                        DYN_DTRS_ERROREXIT,
                        0,
                        -1,
                        dynDTRSet,
                        pdtlv);

        if (rc == NO_ERROR) {
                pdto->dto_dti.dti_lfState |= DTO_ALLSET;
        } else {
                dynDTOUnSet (pdto, pdtlv);
        }

        return (rc);
}


/***LP  dynDTOUnSet - Reverse all work done by dynDTOSet
 *
 *      dynDTOUnSet is used to remove the tracepoints from a module.
 *
 *      All INT 3 opcodes are replaced with their original opcodes,
 *      as needed.
 *
 *      ulong_t dynDTOUnSet     (dto_t *pdto, dtlv_t *pdtlv);
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 *      Pseudocode :
 *
 */

ulong_t dynDTOUnSet     (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        pdto->dto_dti.dti_lfState &= ~DTO_ALLSET;

        // UnSet all set DTRs in this DTO.

        if (pdto->dto_dti.dti_lfState & DTO_SOMESET) {

                // UnSet all the set records.

                dynDTRScan      (pdto,
                                DYN_DTRS_ALIAS |
                                DYN_DTRS_ERRORPROP |
                                DYN_DTRS_ERROREXIT |
                                (DTR_SET << DYN_DTRS_IFSETSHIFT),
                                0,
                                -1,
                                dynDTRUnSet,
                                pdtlv);

                pdto->dto_dti.dti_lfState &= ~DTO_SOMESET;
        }

        return (NO_ERROR);
}


/***LP  dynDTODetach - Detach tracepoints from a module prior to ldrFreeMTE
 *
 *      dynDTODetach removes the DTO from the Scan list, and marks
 *      the tracepoints in the DTO as detached.
 *
 *      Once the DTO has been detached, the DTO is made swappable.
 *
 *      The loader semaphore must be set here.
 *
 *      ulong_t dynDTODetach    (dto_t *pdto, dtlv_t *pdtlv);
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTODetach    (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        // For the level 1 format, we do not reinsert tracepoints.
        // This is done for compatability only.

        if (pdto->dto_dth.dth_Level == DTLEVEL11) {
                // Mark that no tracepoints are set.  This is a
                // simple hack to fool dynDTOUnSet.  Technically,
                // we should unset the individual tracepoints rather
                // then clearing DTO_SOMESET, but hey, it's easy.

                pdto->dto_dti.dti_lfState &= ~DTO_SOMESET;
                return(dynDTODestroy (pdto, pdtlv));
        }

        // Remove the DTO from the scan list.  It may not actually
        // be on the scan list at the current time.

        if (pdto->dto_dti.dti_lfState & DTO_LSCAN) {
                register dto_t *pdtoCur  = pdtoHeadScan;
                register dto_t *pdtoPrev = NULL;

                // Mark as removed from scan list, then actually remove it.

                pdto->dto_dti.dti_lfState &= ~DTO_LSCAN;

                while ((pdtoCur != NULL) && (pdto != pdtoCur)) {
                        pdtoCur = (pdtoPrev = pdtoCur)-> dto_dti.dti_pDTOScan;
                }

                if (pdtoCur != NULL) {
                        if (pdtoPrev == NULL) {
                                pdtoHeadScan = pdtoCur-> dto_dti.dti_pDTOScan;
                        } else {
                                pdtoPrev-> dto_dti.dti_pDTOScan =
                                        pdtoCur-> dto_dti.dti_pDTOScan;
                        }
                }
        }

        // The DTO no longer references a valid module.  Zero dth_hMTE.
        // dti_pchLibName now takes over the responsibility for identifying
        // the module.

        pdto-> dto_dth.dth_hMTE = 0;

        // Not all tracepoints are set

        pdto->dto_dti.dti_lfState &= ~DTO_ALLSET;

        // Mark all the records as detached.  No errors possible here.

        dynDTRScan      (pdto,
                        0,
                        0,
                        -1,
                        dynDTRDetach,
                        pdtlv);

        // No tracepoints are set.

        pdto->dto_dti.dti_lfState &= ~DTO_SOMESET;

        // Set the DTO to swappable.  If we get an error here, we
        // we will not bother to return it.  See dynDTOMakeSwappable.

        dynDTOMakeSwappable (pdto, pdtlv);

        // Set the DTO into the detached state.

        pdto->dto_dti.dti_lfState |= DTO_DETACHED;
        ++cdtoDetached;
        return (NO_ERROR);
}


/***LP  dynDTOAttachCheck - Check if tracepoint attachment required
 *
 *      dynDTOAttachCheck checks the module name in the DTO against
 *      the passed module name.  If a match occurs, the DTO is moved
 *      from the DTO_DETACHED state into the DTO_LOADING state.
 *
 *      ENTRY   pdto    - DTO pointer to check
 *              pdtlv   - Local variables pointer
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOAttachCheck (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        // If detached, and string lengths are equal, and strings compare
        // the same, then move the DTO from DTO_DETACHED to DTO_LOADING,
        // and save the MTE handle in the DTO for later use by DTOAttach.

        if ((pdto->dto_dti.dti_lfState & DTO_DETACHED) &&
            (pdto->dto_dti.dti_cchLibName == pdtlv->dtlv_cchLibName) &&
            !strcmp(pdto->dto_dti.dti_pchLibName, pdtlv->dtlv_pchLibName)) {

                pdto->dto_dth.dth_hMTE = pdtlv->dtlv_hmte;
                pdto->dto_dti.dti_lfState &= ~DTO_DETACHED;
                pdto->dto_dti.dti_lfState |=  DTO_LOADING;
                --cdtoDetached;
                ++cdtoLoading;
        }

        return (NO_ERROR);
}


/***LP  dynDTOAttach - Attach tracepoints in a DTO to a module
 *
 *      dynDTOAttach restores the tracepoints in a module which
 *      was previously freed.  The DTO must not be fixed up at this
 *      point.
 *
 *      Warning: dynDTOAttach can fail.  If this occurs, the load
 *      must fail, and dynDTOAttachError must be called.
 *
 *      ulong_t dynDTOAttach    (pdto, pdtlv)
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, with these fields set:
 *              pdto->dto_dth.dth_hMTE is the module to attach to.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOAttach    (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        ulong_t rc;
        ulong_t cdtl;
        ulong_t fMoved;
        ulong_t ModType;
        register dtl_t *pdtlNew;
        register dtl_t *pdtlOld;

        TKMayBlock();

        if (!(pdto->dto_dti.dti_lfState & DTO_LOADING)) {
                return (NO_ERROR);
        }

        if (NO_ERROR != (rc = LDRGetDYNMteInfo ((VMHOB) pdto->dto_dth.dth_hMTE,
            &(pdtlv->dtlv_padtlNew), SSToDS(&cdtl), SSToDS(&ModType)))) {
                return (rc);
        }

        // Check the number of objects and module type is the same.
        // Note: Be sure to free pdtlv->dtlv_padtlNew on all error paths!

        rc = ERROR_INVALID_DATA;        // ERROR_DYNAMIC_TRACE instead ?

        if ((cdtl != pdto->dto_dti.dti_cdtl) ||
            ((ModType & (DTO_PROGRAM | DTO_DLL)) !=
            (pdto->dto_dti.dti_lfState & (DTO_PROGRAM | DTO_DLL)))) {
                VMFreeKHB (VM_HKH_PUB_RESRW, (void *) (pdtlv->dtlv_padtlNew));
                return (rc);
        }

        // Verify that each locator element is the same, setting the
        // DTL_TRACED bit as required in the new elements.
        // Also, determine if the tracepoints require motion.

        fMoved = FALSE;
        pdtlNew = pdtlv->dtlv_padtlNew;
        pdtlOld = pdtlv->dtlv_padtlOld = pdto->dto_dti.dti_padtl;
        for (; cdtl; cdtl--, pdtlNew++, pdtlOld++) {
                // Set the traced bit as needed.

                pdtlNew-> dtl_fs |= (pdtlOld-> dtl_fs) & DTL_TRACED;

                // Verify the correctness of each object.

                if ((pdtlNew-> dtl_fs != pdtlOld-> dtl_fs) ||
                    (pdtlNew-> dtl_cb != pdtlOld-> dtl_cb)) {
                        VMFreeKHB (VM_HKH_PUB_RESRW,
                        (void *) (pdtlv->dtlv_padtlNew));
                        return (rc);
                }

                // The tracepoints require motion if, for a traced
                // code object, the base linear address has changed.

                fMoved |= ((pdtlNew-> dtl_fs) & DTL_TRACED) &&
                          (pdtlNew-> dtl_pBase != pdtlOld-> dtl_pBase);
        }

        if (fMoved) {
                // Move all the tracepoint records to the new addresses,
                // if any have moved.  This may not fail !

                dynDTRScan      (pdto,
                                0,
                                0,
                                -1,
                                dynDTRMove,
                                pdtlv);
        }

        // Swap the locator arrays, and free the old one.

        pdto->dto_dti.dti_padtl = pdtlv->dtlv_padtlNew;

        VMFreeKHB (VM_HKH_PUB_RESRW, (void *) (pdtlv->dtlv_padtlOld));

        // Set the DTO to resident.  If we get an error, we must
        // return it.

        if (NO_ERROR != (rc = dynDTOMakeResident (pdto, pdtlv))) {
                return (rc);
        }

        // Now we need to put the DTO back on the pdtoHeadScan list.

        pdto->dto_dti.dti_lfState |= DTO_LSCAN;
        pdto->dto_dti.dti_pDTOScan = pdtoHeadScan;
        pdtoHeadScan = pdto;

        // Finally, we attempt to set the tracepoints again.  We need
        // not fix them up this time.  If we get an error, we need to
        // clean up the work we've done already.

        if (NO_ERROR != (rc = dynDTOSet (pdto, pdtlv))) {

                // Error - remove DTO from the scan list.  We know that
                // the DTO of interest is at the top of the scan list.

                dynAssert (pdtoHeadScan == pdto);
                pdto->dto_dti.dti_lfState &= ~DTO_LSCAN;
                pdtoHeadScan = pdto->dto_dti.dti_pDTOScan;

                // The DTO no longer references an MTE.

                pdto->dto_dth.dth_hMTE = 0;

                // We do not need to move the tracepoints back to their
                // old locations.  The new locations are just as valid.

                // Set the DTO back to swappable again.

                dynDTOMakeSwappable (pdto, pdtlv);

                return (rc);
        }

        pdto->dto_dti.dti_lfState &= ~DTO_LOADING;
        --cdtoLoading;
        return (NO_ERROR);
}


/***LP  dynDTOAttachError - Move DTO_LOADING tracepoints to DTO_DETACHED.
 *
 *      dynDTOAttachError is called in case of a load failure to change
 *      any DTOs in the DTO_LOADING state back to DTO_DETACHED.
 *
 *      ulong_t dynDTOAttachError       (pdto, pdtlv)
 *
 *      ENTRY   pdto    - DTO pointer
 *              pdtlv   - Local variables pointer, with these fields set:
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOAttachError (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        if (pdto->dto_dti.dti_lfState & DTO_LOADING) {
                pdto->dto_dth.dth_hMTE = 0;
                pdto->dto_dti.dti_lfState |=  DTO_DETACHED;
                pdto->dto_dti.dti_lfState &= ~DTO_LOADING;
                ++cdtoDetached;
                --cdtoLoading;
        }
        return (NO_ERROR);
}


/***LP  dynDTOPurgeRange - Purge tracepoints from a range of memory
 *
 *      dynDTOPurgeRange is used to purge a range of tracepoints
 *      from a range of memory.
 *
 *      ENTRY   pdto    - DTO pointer to purge
 *              pdtlv   - Local variables pointer, with the folloing set up:
 *                      dtlv_pAlias
 *                      dtlv_pAliasedPoint
 *                      dtlv_lAddrMin
 *                      dtlv_lAddrMax
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOPurgeRange (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        dynDTRScan      (pdto,
                        0,
                        pdtlv->dtlv_lAddrMin,
                        pdtlv->dtlv_lAddrMax,
                        dynDTRPurge,
                        pdtlv);

        return (NO_ERROR);
}


#if 0   // STUB

/***LP  dynDTOReAttachRange - Reattach tracepoints in a range of memory.
 *
 *      ENTRY   pdto    - DTO pointer to reattach
 *              pdtlv   - Local variables pointer.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOReAttachRange (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        ulong_t rc = ERROR_CALL_NOT_IMPLEMENTED;

        TKMayBlock();

        return (rc);
}

#endif  // STUB


/***LP  dynDTOMakeSwappable - Make a tracepoint object swappable
 *
 *      dynDTOMakeSwappable attempts to set a tracepoint object to
 *      swappable.  dynDTOMakeSwappable will return an error if it
 *      failed to set the DTO to swappable.  This error code can
 *      usually be ignored.
 *
 *      Later (if desired) we can identify a needlessly resident
 *      DTO by noticing that DTO_CANSWAP is set and DTO_SWAPPABLE
 *      is clear.  BUGBUG: This optional feature is not implemented.
 *
 *      ENTRY   pdto    - DTO pointer to make swappable
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOMakeSwappable (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        ulong_t rc = NO_ERROR;

        TKMayBlock();

        pdto->dto_dti.dti_lfState |= DTO_CANSWAP;

        // Set the DTO to swappable.  We will set the swappable bit
        // only if it's not already swappable, and PGSetType succeeds.

        if (!(pdto->dto_dti.dti_lfState & DTO_SWAPPABLE) &&
           (NO_ERROR == (rc = PGSetType ((page_t)((ulong_t) pdto >> PAGESHIFT),
            NULL, (pdto-> dto_dti.dti_cbDTO + PAGESIZE - 1) >> PAGESHIFT,
            PG_SWAPPABLE)))) {
                pdto->dto_dti.dti_lfState |= DTO_SWAPPABLE;
        }

        return (rc);
}


/***LP  dynDTOMakeResident - Make a tracepoint object resident
 *
 *      dynDTOMakeResident attempts to set a tracepoint object to
 *      resident.  dynDTOMakeResident will return an error if it
 *      failed to set the DTO to resident.  If an error occurs,
 *      tracepoints may not be set on this module.
 *
 *      ENTRY   pdto    - DTO pointer to make resident
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOMakeResident (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        ulong_t rc = NO_ERROR;

        TKMayBlock();

        // If it's not swappable, we will not call PGSetType.

        if (!(pdto->dto_dti.dti_lfState & DTO_SWAPPABLE)) {
                pdto->dto_dti.dti_lfState &= ~DTO_CANSWAP;
                return (rc);
        }

        // Set the DTO to resident.  If we get an error here, we will
        // not clear DTO_CANSWAP, and must return an error.

        if (NO_ERROR == (rc = PGSetType ((page_t)((ulong_t)pdto >> PAGESHIFT),
            NULL, (pdto-> dto_dti.dti_cbDTO + PAGESIZE - 1) >> PAGESHIFT,
            PG_RESIDENT))) {
                pdto->dto_dti.dti_lfState &= ~(DTO_SWAPPABLE | DTO_CANSWAP);
        }

        return (rc);
}


/***LP  dynDTODestroy - Destroy a tracepoint object
 *
 *      dynDTODestroy destroys a tracepoint object, no matter
 *      what format it may have.  This means that all tracepoints
 *      are removed (as needed), it is completely unlinked, and
 *      the memory is freed.
 *
 *      ENTRY   pdto    - DTO pointer to destroy
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTODestroy (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        if (pdto != NULL) {
                dynDTOUnSet  (pdto, pdtlv);
                dynDTOUnLink (pdto);
                dynDTOFree   (pdto, pdtlv);
        }
        return (NO_ERROR);
}


/***LP  dynDTOFree - Free a tracepoint object
 *
 *      dynDTOFree frees a tracepoint object.  It should have been
 *      removed from the lists at this point.
 *
 *      ENTRY   pdto    - DTO pointer to destroy
 *              pdtlv   - Local variables pointer, not set up.
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOFree      (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        // Free the locator array, if it exists.  (Resident)

        if (pdto->dto_dti.dti_padtl != NULL)
                VMFreeKHB (VM_HKH_PUB_RESRW, pdto->dto_dti.dti_padtl);

        // Free the Library name, if it exists.  (Swappable)

        if (pdto->dto_dti.dti_pchLibName != NULL)
                VMFreeKHB (VM_HKH_PUB_SWAPRW, pdto->dto_dti.dti_pchLibName);

        // Free the dto (Resident)

        VMFreeMem((ulong_t)pdto, HOBNULL, NA);
        return(NO_ERROR);
}


/***LP  dynDTOGetVar - Collect and/or zero tracepoint object variables
 *
 *      dynDTOGetVar copies a tracepoint object's variables to an address
 *      specified in the dtlv, and/or zeroes a tracepoint object's variables.
 *
 *      ENTRY   pdto    - DTO pointer for variables to be collected/zeroed.
 *              pdtlv   - Local variables pointer.
 *
 *      EXIT    ulong_t, standard error code
 *
 *      Optimizations needed:
 *              - keep frequently used sizeof's in variables
 *              - remove indirection (dereferences) where possible
 */

ulong_t dynDTOGetVar    (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        register unsigned index;
        unsigned long     clVar;
        unsigned long     clReqVar;
        char              *pVar;
        unsigned long     cbResult;
        struct vmlh_s     hlockbuf;
        dtgp_t            dtgp;
        ulong_t           rc = NO_ERROR;

        TKMayBlock();

        // Validate requested variable range.  Requested start index should be
        // less than the number of variables.  Requested number of variables,
        // starting from the requested start index, should not exceed the
        // actual number of variables.

        index = pdtlv->dtlv_pdtgp->dtgp_ulStartIdx;   // DT variable start index
        clVar = pdto->dto_dth.dth_clVar;              // actual variable cnt
        if (index >= clVar)
                return(ERROR_INVALID_DATA);

        clReqVar = pdtlv->dtlv_pdtgp->dtgp_clVar;    // requested variable cnt
        if (clReqVar == DT_ALLVAR)
                clReqVar = clVar - index;            // to end of variables
        else
        if ((index + clReqVar) > clVar)
                return(ERROR_INVALID_DATA);

        // Point to the first requested variable.

        pVar = (PCHAR)&pdto->dto_dth + sizeof(dth_t) +
                        (index * sizeof(ulong_t));

        if (pdtlv->dtlv_ulCmd & DTGH_GET)
        {
                // Verify that the size of the result buffer is sufficient
                // to hold a dtgp structure followed by the DWORD variables.
                // We don't want to copy out partial information.

                cbResult = sizeof(dtgp) + (clReqVar * sizeof(ulong_t));
                if ((cbResult + pdtlv->dtlv_cbResult) > pdtlv->dtlv_cbAllocRes)
                        return(ERROR_BUFFER_OVERFLOW);

                // Set up the return dtgp structure.

                dtgp.dtgp_hMTE = pdto->dto_dth.dth_hMTE;
                dtgp.dtgp_usID = pdto->dto_dth.dth_ID;
                dtgp.dtgp_ulStartIdx = index;
                dtgp.dtgp_clVar = clReqVar;

                // Lock down destination for dtgp structure and variable copy.

                if (NO_ERROR != (rc = VMLockMem(
                    (ulong_t) pdtlv->dtlv_pResultUser,
                    cbResult, VML_VERIFY | VML_W,
                    SSToDS(&hlockbuf),
                    (struct pagelist_s *) VM_NO_ADDR,
                    (ulong_t *) VM_NO_ADDR)))
                        return(rc);

                // Copy dtgp structure followed by the tracepoint object
                // variables.

                rc = TKSuBuff((PCHAR) pdtlv->dtlv_pResultUser,
                                (PCHAR)SSToDS(&dtgp), sizeof(dtgp),TK_UM_FATAL);
                if (rc == NO_ERROR)
                {
                        // Copy the variables.

                        pdtlv->dtlv_pResultUser += sizeof(dtgp);
                        cbResult -= sizeof(dtgp);       // Bytes of variables
                        rc = TKSuBuff((PCHAR)pdtlv->dtlv_pResultUser,
                                (PCHAR)pVar, cbResult, TK_UM_FATAL);
                }

                // Unlock previously locked portion of the result buffer.

                VMUnlock(SSToDS(&hlockbuf));

                if (rc != NO_ERROR)
                        return(rc);

                // Set up dtlv to reflect the current offset into the
                // result packet, and the number of bytes written to
                // the result packet.

                pdtlv->dtlv_pResultUser += cbResult;
                pdtlv->dtlv_cbResult += cbResult + sizeof(dtgp);
        }

        // Zero the tracepoint object variables.

        if (pdtlv->dtlv_ulCmd & DTGH_ZERO)
                memset((void *) pVar, 0, (clReqVar * sizeof(ulong_t)));

        return(NO_ERROR);

}


/***LP  dynDTOQuery - Query the system for tracepoint objects
 *
 *      dynDTOQuery copies a tracepoint object's module handle, TDF
 *      identifier, and module pathname (zero-terminated) to an address
 *      specified in the dtlv.
 *
 *      ENTRY   pdto    - DTO pointer for tracepoint objects queried.
 *              pdtlv   - Local variables pointer.
 *
 *      EXIT    ulong_t, standard error code
 *
 *      Optimizations needed:
 *              - keep frequently used sizeof's in variables
 *              - remove indirection (dereferences) where possible
 */

ulong_t dynDTOQuery     (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        unsigned long     cbResult;
        struct vmlh_s     hlockbuf;
        dtqo_t            dtqo;
        ulong_t           rc = NO_ERROR;

        TKMayBlock();

        // Set up the return dtqo structure.

        dtqo.dtqo_hMTE = pdto->dto_dth.dth_hMTE;
        dtqo.dtqo_usID = pdto->dto_dth.dth_ID;
        dtqo.dtqo_clVar = pdto->dto_dth.dth_clVar;

        // Set length of pathname.  Note that cchLibName is the count of
        // bytes in the pathname, exclusive of the zero-terminating
        // byte.  Add 1 to include zero-terminator in copy.

        dtqo.dtqo_cbPathname = pdto->dto_dti.dti_cchLibName + 1;

        cbResult = sizeof(dtqo_t) + dtqo.dtqo_cbPathname;

        // Copy out the dtqo structure.

        rc = TKSuBuff((PCHAR) pdtlv->dtlv_pResultUser, (PCHAR)SSToDS(&dtqo),
                        sizeof(dtqo), TK_UM_FATAL);

        if (rc == NO_ERROR)
        {
                // Copy the pathname.

                pdtlv->dtlv_pResultUser += sizeof(dtqo);  // Point past dtqo

                rc = TKSuBuff((PCHAR)pdtlv->dtlv_pResultUser,
                        (PCHAR)pdto->dto_dti.dti_pchLibName,
                        dtqo.dtqo_cbPathname, TK_UM_FATAL);
        }

        if (rc == NO_ERROR)
        {
                // Set up dtlv to reflect the current offset into the
                // result packet, and the number of bytes written to
                // the result packet.

                pdtlv->dtlv_pResultUser += dtqo.dtqo_cbPathname;
                pdtlv->dtlv_cbResult += cbResult;
        }

        return(rc);
}

/***LP  dynDTONullProc - Null worker for dynDTOScan
 *
 *      ENTRY   pdto    - DTO pointer to destroy
 *              pdtlv   - Local variables pointer, optional
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTONullProc  (pdto, pdtlv)
dto_t   *pdto;
dtlv_t  *pdtlv;
{
        TKMayBlock();

        return (NO_ERROR);
}


/***LP  dynDTOScan - Scan a DTO list
 *
 *      The worker may unlink or move the current DTO, but may not
 *      unlink the previous DTO in the list.  DTOs which are added
 *      to the list may not be properly scanned.
 *
 *      Set lAddrMin = 0 and lAddrMax = (ulong_t) -1 to ignore address
 *      ranges.
 *
 *      WORKER ENTRY
 *              (*pfnWorker) (dto_t *, ulong_t *);
 *
 *      ENTRY
 *              ppdtoHead       : dto list to scan
 *              flCmd           : Scan options.  See dynlocal.h
 *                      DYN_DTOS_SHOULDEXIST : Error if DTO not found
 *                      DYN_DTOS_MUSTEXIST   : Panic if DTO not found
 *                      DYN_DTOS_MATCHMTE    : Match hmte field in DTO
 *                      DYN_DTOS_MATCHID     : Match ID field in DTO
 *                      DYN_DTOS_ERRORPROP   : Propogate DTO worker errors
 *                      DYN_DTOS_ERROREXIT   : Exit if DTO worker error
 *              lAddrMin        : Minimum address of range (inclusive)
 *              lAddrMax        : Maximum address of range (inclusive)
 *              pfnWorker       : Worker routine
 *              pdtlv           : Data to pass to worker
 *
 *      EXIT    ulong_t, standard error code
 *
 */

ulong_t dynDTOScan (ppdtoHead, flCmd, lAddrMin, lAddrMax, pfnWorker, pdtlv)
dto_t   **ppdtoHead;
ulong_t flCmd;
ulong_t lAddrMin;
ulong_t lAddrMax;
ulong_t (*pfnWorker) (dto_t *, dtlv_t *);
dtlv_t  *pdtlv;
{
        dto_t   *pdto;
        dto_t   **ppdto;
        ulong_t fExists = FALSE;
        ulong_t rc = NO_ERROR;

        TKMayBlock();

        ppdto = ppdtoHead;
        pdto = *ppdto;
        while (pdto != NULL) {

                // Check scan options, and call the worker.

                if ((pdto->dto_dti.dti_lAddrMin <= lAddrMax) &&
                    (pdto->dto_dti.dti_lAddrMax >= lAddrMin) &&
                    ((flCmd & DYN_DTOS_MATCHMTE) ?
                     (pdto->dto_dth.dth_hMTE == pdtlv->dtlv_hmte) : TRUE) &&
                    ((flCmd & DYN_DTOS_MATCHID) ?
                     ((pdtlv->dtlv_ID == DT_NZID) ?
                      (pdto->dto_dth.dth_ID != 0) :
                      (pdto->dto_dth.dth_ID == pdtlv->dtlv_ID)) : TRUE) ) {

                        register ulong_t rcTemp;
                        if (NO_ERROR != (rcTemp = (*pfnWorker)(pdto, pdtlv))) {
                                if (rc == NO_ERROR)
                                        rc = rcTemp;    // Catch earliest error
                                fExists = TRUE;
                                if (flCmd & DYN_DTOS_ERROREXIT)
                                        break;
                        }
                        fExists = TRUE;
                }

                // Move to next DTO.  If the current one was unlinked,
                // we must be careful not to move ppdto.

                if (pdto == *ppdto) {
                        ppdto = (ppdtoHead == &pdtoHeadAll) ?
                                &((*ppdto)->dto_dti.dti_pDTONext):
                                &((*ppdto)->dto_dti.dti_pDTOScan);
                }

                pdto = *ppdto;
        }

        // If DYN_DTOS_ERRORPROP is clear, ignore worker errors.

        if (!(flCmd & DYN_DTOS_ERRORPROP))
                rc = NO_ERROR;

        // If the worker was not called, determine whether this is
        // normal, an error, or a panic, and return the appropriate info.

        if (!fExists) {
                if (flCmd & DYN_DTOS_SHOULDEXIST)
                        rc = ERROR_INVALID_HANDLE;
                if (flCmd & DYN_DTOS_MUSTEXIST)
                        panic ("dynDTOScan: Not found");
        }

        return (rc);
}

#endif  // RAS
