/*
 * Copyright (C) 2016-2018 Advanced Micro Devices, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "KFDDBGTest.hpp"
#include <sys/ptrace.h>
#include <poll.h>
#include "KFDQMTest.hpp"
#include "PM4Queue.hpp"
#include "PM4Packet.hpp"
#include "Dispatch.hpp"
#include <string>

static const char* loop_inc_isa = \
"\
shader loop_inc_isa\n\
asic(VI)\n\
type(CS)\n\
vgpr_count(16)\n\
trap_present(1)\n\
/* TODO Enable here Address Watch Exception: */ \n\
/*copy the parameters from scalar registers to vector registers*/\n\
    v_mov_b32  v0, s0\n\
    v_mov_b32  v1, s1\n\
    v_mov_b32  v2, s2\n\
    v_mov_b32  v3, s3\n\
    v_mov_b32  v5, 0 \n\
/*Iteration 1*/\n\
    v_mov_b32 v4, -1\n\
    flat_atomic_inc v5, v[0:1], v4 glc \n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    flat_load_dword v8, v[2:3] \n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    v_cmp_gt_u32 vcc, 2, v5 \n\
/*Iteration 2*/\n\
    v_mov_b32 v4, -1\n\
    flat_atomic_inc v5, v[0:1], v4 glc \n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    flat_load_dword v8, v[2:3] \n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    v_cmp_gt_u32 vcc, 2, v5 \n\
/*Epilogue*/\n\
    v_mov_b32 v5, 0 \n\
    s_endpgm\n\
    \n\
end\n\
";

static const char* iterate_isa_gfx9 = \
"\
shader iterate_isa\n\
asic(GFX9)\n\
type(CS)\n\
/*copy the parameters from scalar registers to vector registers*/\n\
    v_mov_b32 v0, s0\n\
    v_mov_b32 v1, s1\n\
    v_mov_b32 v2, s2\n\
    v_mov_b32 v3, s3\n\
    flat_load_dword v4, v[0:1] slc    /*load target iteration value*/\n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    v_mov_b32 v5, 0\n\
LOOP:\n\
    v_add_co_u32 v5, vcc, 1, v5\n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    /*compare the result value (v5) to iteration value (v4),*/\n\
    /*and jump if equal (i.e. if VCC is not zero after the comparison)*/\n\
    v_cmp_lt_u32 vcc, v5, v4\n\
    s_cbranch_vccnz LOOP\n\
    flat_store_dword v[2,3], v5\n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    s_endpgm\n\
    end\n\
";

static const char* jump_to_trap_gfx9 = \
"\
shader jump_to_trap\n\
asic(GFX9)\n\
type(CS)\n\
/*copy the parameters from scalar registers to vector registers*/\n\
    s_trap 1\n\
    v_mov_b32 v0, s0\n\
    v_mov_b32 v1, s1\n\
    v_mov_b32 v2, s2\n\
    v_mov_b32 v3, s3\n\
    flat_load_dword v4, v[0:1] slc    /*load target iteration value*/\n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    v_mov_b32 v5, 0\n\
LOOP:\n\
    v_add_co_u32 v5, vcc, 1, v5\n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    /*compare the result value (v5) to iteration value (v4),*/\n\
    /*and jump if equal (i.e. if VCC is not zero after the comparison)*/\n\
    v_cmp_lt_u32 vcc, v5, v4\n\
    s_cbranch_vccnz LOOP\n\
    flat_store_dword v[2,3], v5\n\
    s_waitcnt vmcnt(0)&lgkmcnt(0)\n\
    s_endpgm\n\
    end\n\
";

static const char* trap_handler_gfx9 = \
"\
shader trap_handler\n\
asic(GFX9)\n\
type(CS)\n\
CHECK_VMFAULT:\n\
    /*if trap jumped to by vmfault, restore skip m0 signalling*/\n\
    s_getreg_b32 ttmp14, hwreg(HW_REG_TRAPSTS)\n\
    s_and_b32 ttmp2, ttmp14, 0x800\n\
    s_cbranch_scc1 RESTORE_AND_EXIT\n\
GET_DOORBELL:\n\
    s_mov_b32 ttmp2, exec_lo\n\
    s_mov_b32 ttmp3, exec_hi\n\
    s_mov_b32 exec_lo, 0x80000000\n\
    s_sendmsg 10\n\
WAIT_SENDMSG:\n\
    /*wait until msb is cleared (i.e. doorbell fetched)*/\n\
    s_nop 7\n\
    s_bitcmp0_b32 exec_lo, 0x1F\n\
    s_cbranch_scc0 WAIT_SENDMSG\n\
SEND_INTERRUPT:\n\
    /* set context bit and doorbell and restore exec*/\n\
    s_mov_b32 exec_hi, ttmp3\n\
    s_and_b32 exec_lo, exec_lo, 0xfff\n\
    s_mov_b32 ttmp3, exec_lo\n\
    s_bitset1_b32 ttmp3, 23\n\
    s_mov_b32 exec_lo, ttmp2\n\
    s_mov_b32 ttmp2, m0\n\
    /* set m0, send interrupt and restore m0 and exit trap*/\n\
    s_mov_b32 m0, ttmp3\n\
    s_nop 0x0\n\
    s_sendmsg sendmsg(MSG_INTERRUPT)\n\
    s_mov_b32 m0, ttmp2\n\
RESTORE_AND_EXIT:\n\
    /* restore and increment program counter to skip shader trap jump*/\n\
    s_add_u32 ttmp0, ttmp0, 4\n\
    s_addc_u32 ttmp1, ttmp1, 0\n\
    s_and_b32 ttmp1, ttmp1, 0xffff\n\
    /* restore SQ_WAVE_IB_STS */\n\
    s_lshr_b32 ttmp2, ttmp11, (26 - 15)\n\
    s_and_b32 ttmp2, ttmp2, (0x8000 | 0x1F0000)\n\
    s_setreg_b32 hwreg(HW_REG_IB_STS), ttmp2\n\
    /* restore SQ_WAVE_STATUS */\n\
    s_and_b64 exec, exec, exec\n\
    s_and_b64 vcc, vcc, vcc\n\
    s_setreg_b32 hwreg(HW_REG_STATUS), ttmp12\n\
    s_rfe_b64 [ttmp0, ttmp1]\n\
    end\n\
";

void KFDDBGTest::SetUp() {
    ROUTINE_START

    KFDBaseComponentTest::SetUp();

    m_pIsaGen = IsaGenerator::Create(m_FamilyId);

    ROUTINE_END
}

void KFDDBGTest::TearDown() {
    ROUTINE_START
    if (m_pIsaGen)
        delete m_pIsaGen;
    m_pIsaGen = NULL;

    /* Reset the user trap handler */
    hsaKmtSetTrapHandler(m_NodeInfo.HsaDefaultGPUNode(), 0, 0, 0, 0);

    KFDBaseComponentTest::TearDown();

    ROUTINE_END
}

#if 0
// Functionality is deprecated now, keeping code for reference
TEST_F(KFDDBGTest, BasicAddressWatch) {
    TEST_START(TESTPROFILE_RUNALL)
    if (m_FamilyId >= FAMILY_VI) {
        int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode();
        static const unsigned int TMA_TRAP_COUNT_OFFSET        = 3;
        static const unsigned int TMA_TRAP_COUNT_VALUE         = 3;

        ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node";

        // Allocate Buffers on System Memory
        HsaMemoryBuffer trapHandler(PAGE_SIZE*2, defaultGPUNode, true/*zero*/, false/*local*/, true/*exec*/);
        HsaMemoryBuffer isaBuf(PAGE_SIZE, defaultGPUNode, true/*zero*/, false/*local*/, true/*exec*/);
        HsaMemoryBuffer dstBuf(PAGE_SIZE*4, defaultGPUNode, true, false, false);
        HsaMemoryBuffer tmaBuf(PAGE_SIZE, defaultGPUNode, false, false, false);

        // Get Address Watch TrapHandler
        m_pIsaGen->GetAwTrapHandler(trapHandler);

        ASSERT_SUCCESS(hsaKmtSetTrapHandler(defaultGPUNode,
                                            trapHandler.As<void *>(),
                                            0x1000,
                                            tmaBuf.As<void*>(),
                                            0x1000));

        m_pIsaGen->CompileShader(loop_inc_isa, "loop_inc_isa", isaBuf);
        PM4Queue queue, queue_flush;

        ASSERT_SUCCESS(queue.Create(defaultGPUNode));
        ASSERT_SUCCESS(queue_flush.Create(defaultGPUNode));

        // Set Address Watch Params
        // TODO: Set WatchMode[1] to Atomic in case we want to test this mode.

        HSA_DBG_WATCH_MODE  WatchMode[2];
        HSAuint64           WatchAddress[2];
        HSAuint64           WatchMask[2];
        HSAKMT_STATUS       AddressWatchSuccess;
        HSAuint64           AddressMask64 = 0x0;
        unsigned char *secDstBuf = (unsigned char *)dstBuf.As<void*>() + 8192;

        WatchMode[0]    = HSA_DBG_WATCH_ALL;
        WatchAddress[0] = (HSAuint64) dstBuf.As<void*>() & (~AddressMask64);
        WatchMask[0]    = ~AddressMask64;
        WatchMode[1]    = HSA_DBG_WATCH_ALL;
        WatchAddress[1] = (HSAuint64)secDstBuf & (~AddressMask64);
        WatchMask[1]    = ~AddressMask64;

        queue_flush.PlaceAndSubmitPacket(PM4WriteDataPacket(dstBuf.As<unsigned int*>(), 0x0, 0x0));
        queue_flush.PlaceAndSubmitPacket(PM4WriteDataPacket((unsigned int *)secDstBuf, 0x0, 0x0));
        Delay(50);
        ASSERT_SUCCESS(hsaKmtDbgRegister(defaultGPUNode));

        AddressWatchSuccess = hsaKmtDbgAddressWatch(
                defaultGPUNode,                                 // IN
                2,                                              // # watch points
                &WatchMode[0],                                  // IN
                reinterpret_cast<void **>(&WatchAddress[0]),    // IN
                &WatchMask[0],                                  // IN, optional
                NULL);                                          // IN, optional

        EXPECT_EQ(AddressWatchSuccess, HSAKMT_STATUS_SUCCESS);

        Dispatch dispatch(isaBuf);
        dispatch.SetArgs(dstBuf.As<void*>(), reinterpret_cast<void *>(secDstBuf));
        dispatch.SetDim(1, 1, 1);

        /* TODO: Use Memory ordering rules w/ atomics for host-GPU memory syncs.
         * Set to std::memory_order_seq_cst
         */

        dispatch.Submit(queue);

        Delay(50);
        dispatch.Sync(g_TestTimeOut);

        // Check that we got trap handler calls due to add watch triggers
        EXPECT_GE(*(tmaBuf.As<unsigned int*>()+ TMA_TRAP_COUNT_OFFSET), TMA_TRAP_COUNT_VALUE);

        EXPECT_SUCCESS(hsaKmtDbgUnregister(defaultGPUNode));
        EXPECT_SUCCESS(queue.Destroy());
        EXPECT_SUCCESS(queue_flush.Destroy());
    } else {
        LOG() << "Skipping test: Test not supported on family ID 0x" << m_FamilyId << "." << std::endl;
    }
    TEST_END
}
#endif

TEST_F(KFDDBGTest, BasicDebuggerSuspendResume) {
    TEST_START(TESTPROFILE_RUNALL)
    if (m_FamilyId >= FAMILY_AI) {
        int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode();

        ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node";

        HSAuint32 Flags = 0;
        HsaMemoryBuffer isaBuffer(PAGE_SIZE, defaultGPUNode, true/*zero*/, false/*local*/, true/*exec*/);
        HsaMemoryBuffer iterateBuf(PAGE_SIZE, defaultGPUNode, true, false, false);
        HsaMemoryBuffer resultBuf(PAGE_SIZE, defaultGPUNode, true, false, false);

        unsigned int* iter = iterateBuf.As<unsigned int*>();
        unsigned int* result = resultBuf.As<unsigned int*>();

        int suspendTimeout = 500;
        int syncStatus;

        m_pIsaGen->CompileShader(iterate_isa_gfx9, "iterate_isa", isaBuffer);

        PM4Queue queue1;
        HsaQueueResource *qResources;
        HSA_QUEUEID queue_ids[2];

        ASSERT_SUCCESS(queue1.Create(defaultGPUNode));

        Dispatch *dispatch1;

        dispatch1 = new Dispatch(isaBuffer);

        dispatch1->SetArgs(&iter[0], &result[0]);
        dispatch1->SetDim(1, 1, 1);

        // Need a loop large enough so we don't finish before we call Suspend.
        //  150000000 takes between 5 and 6 seconds, which is long enough
        //  to test the suspend/resume.
        iter[0] = 150000000;

        ASSERT_EQ(ptrace(PTRACE_TRACEME, 0, 0, 0), 0);
        ASSERT_SUCCESS(hsaKmtEnableDebugTrap(defaultGPUNode, INVALID_QUEUEID));

        // Submit the shader, queue1
        dispatch1->Submit(queue1);
        qResources = queue1.GetResource();
        queue_ids[0] = qResources->QueueId;

        ASSERT_SUCCESS(hsaKmtQueueSuspend(
                    INVALID_PID,
                    1,  // one queue
                    queue_ids,
                    10,  // grace period
                    Flags));

        syncStatus = dispatch1->SyncWithStatus(suspendTimeout);
        ASSERT_NE(syncStatus, HSAKMT_STATUS_SUCCESS);

        ASSERT_NE(iter[0], result[0]);

        // The shader hasn't finished, we will wait for 20 seconds,
        // and then check if it has finished.  If it was suspended,
        // it should not have finished.
        Delay(20000);

        // Check that the shader has not finished yet.
        syncStatus = dispatch1->SyncWithStatus(suspendTimeout);
        ASSERT_NE(syncStatus, HSAKMT_STATUS_SUCCESS);

        ASSERT_NE(iter[0], result[0]);

        ASSERT_SUCCESS(hsaKmtQueueResume(
                    INVALID_PID,
                    1,  // Num queues
                    queue_ids,
                    Flags));

        dispatch1->Sync();
        ASSERT_EQ(iter[0], result[0]);

        EXPECT_SUCCESS(queue1.Destroy());

        ASSERT_SUCCESS(hsaKmtDisableDebugTrap(defaultGPUNode));

    } else {
        LOG() << "Skipping test: Test not supported on family ID 0x" << m_FamilyId << "." << std::endl;
    }
    TEST_END
}

TEST_F(KFDDBGTest, BasicDebuggerQueryQueueStatus) {
    TEST_START(TESTPROFILE_RUNALL)
    if (m_FamilyId >= FAMILY_AI) {
        int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode();
        HSAint32 PollFd;

        ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node";

        // enable debug trap and check poll fd creation
        ASSERT_SUCCESS(hsaKmtEnableDebugTrapWithPollFd(defaultGPUNode,
                                                       INVALID_QUEUEID,
                                                       &PollFd));
        ASSERT_GT(PollFd, 0) << "failed to create polling file descriptor";

        // create shader and trap bufs then enable 2nd level trap
        HsaMemoryBuffer isaBuf(PAGE_SIZE, defaultGPUNode, true, false, true);
        HsaMemoryBuffer iterBuf(PAGE_SIZE, defaultGPUNode, true, false, false);
        HsaMemoryBuffer resBuf(PAGE_SIZE, defaultGPUNode, true, false, false);

        HsaMemoryBuffer trap(PAGE_SIZE*2, defaultGPUNode, true, false, true);
        HsaMemoryBuffer tmaBuf(PAGE_SIZE, defaultGPUNode, false, false, false);

        ASSERT_SUCCESS(hsaKmtSetTrapHandler(defaultGPUNode,
                                            trap.As<void *>(),
                                            0x1000,
                                            tmaBuf.As<void*>(),
                                            0x1000));

        // compile and dispatch shader
        m_pIsaGen->CompileShader(jump_to_trap_gfx9, "jump_to_trap", isaBuf);
        m_pIsaGen->CompileShader(trap_handler_gfx9, "trap_handler", trap);

        PM4Queue queue;
        HsaQueueResource *qResources;
        ASSERT_SUCCESS(queue.Create(defaultGPUNode));

        unsigned int* iter = iterBuf.As<unsigned int*>();
        unsigned int* result = resBuf.As<unsigned int*>();
        int suspendTimeout = 500;
        int syncStatus;
        iter[0] = 150000000;
        Dispatch *dispatch;
        dispatch = new Dispatch(isaBuf);
        dispatch->SetArgs(&iter[0], &result[0]);
        dispatch->SetDim(1, 1, 1);

        dispatch->Submit(queue);
        qResources = queue.GetResource();

        // poll, read and query for pending trap event
        struct pollfd fds[1];
        fds[0].fd = PollFd;
        fds[0].events = POLLIN | POLLRDNORM;
        ASSERT_GT(poll(fds, 1, 5000), 0);

        char trapChar;
        ASSERT_GT(read(PollFd, &trapChar, 1), 0);
        ASSERT_EQ('t', trapChar);

        HSAuint32 invalidQid = 0xffffffff;
        HSAuint32 qid = invalidQid;
        HSA_QUEUEID queueIds[1] = { qResources->QueueId};
        HSA_DEBUG_EVENT_TYPE EventReceived;
        bool IsSuspended = false;
        bool IsNew = false;

        ASSERT_SUCCESS(hsaKmtQueryDebugEvent(defaultGPUNode, INVALID_PID, &qid,
                                             false, &EventReceived,
                                             &IsSuspended, &IsNew));
        ASSERT_NE(qid, invalidQid);
        ASSERT_EQ(IsSuspended, false);
        ASSERT_EQ(IsNew, true);
        ASSERT_EQ(EventReceived, HSA_DEBUG_EVENT_TYPE_TRAP);

        // suspend queue, query suspended queue and clear pending event
        ASSERT_SUCCESS(hsaKmtQueueSuspend(INVALID_PID, 1, queueIds, 10, 0));

        syncStatus = dispatch->SyncWithStatus(suspendTimeout);
        ASSERT_NE(syncStatus, HSAKMT_STATUS_SUCCESS);
        ASSERT_NE(iter[0], result[0]);

        ASSERT_SUCCESS(hsaKmtQueryDebugEvent(defaultGPUNode, INVALID_PID,
                                             &qid, true, &EventReceived,
                                             &IsSuspended, &IsNew));
        ASSERT_EQ(IsSuspended, true);

        ASSERT_SUCCESS(hsaKmtQueueResume(INVALID_PID, 1, queueIds, 0));

        ASSERT_SUCCESS(hsaKmtQueryDebugEvent(defaultGPUNode, INVALID_PID, &qid,
                                             false, &EventReceived,
                                             &IsSuspended, &IsNew));

        ASSERT_EQ(IsSuspended, false);
        ASSERT_EQ(IsNew, false);
        ASSERT_EQ(EventReceived, HSA_DEBUG_EVENT_TYPE_NONE);

        dispatch->Sync();
        ASSERT_EQ(iter[0], result[0]);
        EXPECT_SUCCESS(queue.Destroy());
        ASSERT_SUCCESS(hsaKmtDisableDebugTrap(defaultGPUNode));
        ASSERT_EQ(close(PollFd), 0);

    } else {
        LOG() << "Skipping test: Test not supported on family ID 0x"
              << m_FamilyId << "." << std::endl;
    }
    TEST_END
}

// clean up routine
static void ExitVMFaultQueryChild(std::string errMsg,
                                  int exitStatus,
                                  HSAint32 pollFd,
                                  PM4Queue *queue1,
                                  PM4Queue *queue2,
                                  HsaEvent *event,
                                  int gpuNode) {
    if (queue1)
        queue1->Destroy();

    if (queue2)
        queue2->Destroy();

    if (event) {
        int ret = hsaKmtDestroyEvent(event);
        if (ret) {
            exitStatus = 1;
            errMsg = "event failed to be destroyed";
        }
    }

    if (pollFd >= 0)
        close(pollFd);

    if (gpuNode >= 0) {
        int ret = hsaKmtDisableDebugTrap(gpuNode);
        if (ret) {
            exitStatus = 1;
            errMsg = "debug trap failed to disable";
        }
    }

    if (!errMsg.empty())
        WARN() << errMsg << std::endl;

    exit(exitStatus);
}

TEST_F(KFDDBGTest, BasicDebuggerQueryVMFaultQueueStatus) {
    TEST_START(TESTPROFILE_RUNALL)
    if (m_FamilyId >= FAMILY_AI) {
        int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode();

        ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node";

        pid_t childPid = fork();
        ASSERT_GE(childPid, 0);

        // fork to child since vm faults halt all queues
        if (childPid == 0) {
            HSAint32 PollFd;
            HSAKMT_STATUS ret;
            bool childStatus;

            ret = hsaKmtOpenKFD();
            if (ret != HSAKMT_STATUS_SUCCESS)
                ExitVMFaultQueryChild("KFD open failed",
                                      1, -1, NULL, NULL, NULL, -1);

            // enable debug trap and check poll fd creation
            ret = hsaKmtEnableDebugTrapWithPollFd(defaultGPUNode,
                                                  INVALID_QUEUEID,
                                                  &PollFd);

            if (ret != HSAKMT_STATUS_SUCCESS || PollFd <= 0)
                ExitVMFaultQueryChild("enable debug trap with poll fd failed",
                                      1, -1, NULL, NULL, NULL, defaultGPUNode);

            // create shader, vmfault and trap bufs then enable 2nd level trap
            HsaMemoryBuffer vmFaultBuf(PAGE_SIZE, defaultGPUNode, true, false,
                                       true);
            HsaMemoryBuffer srcBuf(PAGE_SIZE, defaultGPUNode, false);
            srcBuf.Fill(0xABCDABCD);

            HsaMemoryBuffer isaBuf(PAGE_SIZE, defaultGPUNode, true, false,
                                   true);
            HsaMemoryBuffer iterBuf(PAGE_SIZE, defaultGPUNode, true, false,
                                    false);
            HsaMemoryBuffer resBuf(PAGE_SIZE, defaultGPUNode, true, false,
                                   false);

            HsaMemoryBuffer trap(PAGE_SIZE*2, defaultGPUNode, true, false,
                                 true);
            HsaMemoryBuffer tmaBuf(PAGE_SIZE, defaultGPUNode, false, false,
                                   false);

            ret = hsaKmtSetTrapHandler(defaultGPUNode,
                                       trap.As<void *>(), 0x1000,
                                       tmaBuf.As<void*>(), 0x1000);

            if (ret != HSAKMT_STATUS_SUCCESS)
                ExitVMFaultQueryChild("setting trap handler failed",
                                      1, PollFd, NULL, NULL, NULL,
                                      defaultGPUNode);

            // compile and dispatch shader
            m_pIsaGen->CompileShader(jump_to_trap_gfx9, "jump_to_trap",
                                     isaBuf);
            m_pIsaGen->CompileShader(trap_handler_gfx9, "trap_handler", trap);

            PM4Queue queue1, queue2;
            HSAuint32 qid1;
            if (queue1.Create(defaultGPUNode) != HSAKMT_STATUS_SUCCESS)
                ExitVMFaultQueryChild("queue 1 creation failed",
                                      1, PollFd, NULL, NULL, NULL,
                                      defaultGPUNode);

            if (queue2.Create(defaultGPUNode) != HSAKMT_STATUS_SUCCESS)
                ExitVMFaultQueryChild("queue 2 creation failed",
                                      1, PollFd, &queue1, NULL, NULL,
                                      defaultGPUNode);

            unsigned int* iter = iterBuf.As<unsigned int*>();
            unsigned int* result = resBuf.As<unsigned int*>();
            int suspendTimeout = 500;
            iter[0] = 150000000;
            Dispatch *dispatch1;
            dispatch1 = new Dispatch(isaBuf);
            dispatch1->SetArgs(&iter[0], &result[0]);
            dispatch1->SetDim(1, 1, 1);
            dispatch1->Submit(queue1);

            // poll, read and query pending trap event
            struct pollfd fds[1];
            fds[0].fd = PollFd;
            fds[0].events = POLLIN | POLLRDNORM;
            if (poll(fds, 1, 5000) <= 0)
                ExitVMFaultQueryChild("poll wake on pending trap event failed",
                                      1, PollFd, &queue1, &queue2, NULL,
                                      defaultGPUNode);

            int kMaxSize = 4096;
            char fifoBuf[kMaxSize];
            childStatus = read(PollFd, fifoBuf, 1) == -1\
                          || strchr(fifoBuf, 't') == NULL;
            if (childStatus)
                ExitVMFaultQueryChild("read on pending trap event failed",
                                      1, PollFd, &queue1, &queue2, NULL,
                                      defaultGPUNode);

            memset(fifoBuf, 0, sizeof(fifoBuf));

            HSA_DEBUG_EVENT_TYPE EventReceived;
            bool IsSuspended;
            bool IsNew;
            HSAuint32 invalidQid = 0xffffffff;
            qid1 = invalidQid;

            ret = hsaKmtQueryDebugEvent(defaultGPUNode, INVALID_PID, &qid1,
                                        false, &EventReceived, &IsSuspended,
                                        &IsNew);

            childStatus = ret != HSAKMT_STATUS_SUCCESS
                                 || EventReceived != HSA_DEBUG_EVENT_TYPE_TRAP;
            if (childStatus)
                ExitVMFaultQueryChild("query on pending trap event failed",
                                      1, PollFd, &queue1, &queue2, NULL,
                                      defaultGPUNode);

            // create and wait on pending vmfault event
            HsaEvent *vmFaultEvent;
            HsaEventDescriptor eventDesc;
            eventDesc.EventType = HSA_EVENTTYPE_MEMORY;
            eventDesc.NodeId = defaultGPUNode;
            eventDesc.SyncVar.SyncVar.UserData = NULL;
            eventDesc.SyncVar.SyncVarSize = 0;
            ret = hsaKmtCreateEvent(&eventDesc, true, false,
                                            &vmFaultEvent);
            if (ret != HSAKMT_STATUS_SUCCESS)
                ExitVMFaultQueryChild("create vmfault event failed",
                                      1, PollFd, &queue1, &queue2, NULL,
                                      defaultGPUNode);

            Dispatch dispatch2(vmFaultBuf, false);
            dispatch2.SetArgs(
                reinterpret_cast<void *>(srcBuf.As<HSAuint64>()),
                reinterpret_cast<void *>(0xABBAABBAULL));
            dispatch2.SetDim(1, 1, 1);
            dispatch2.Submit(queue2);

            ret = hsaKmtWaitOnEvent(vmFaultEvent, g_TestTimeOut);
            if (ret != HSAKMT_STATUS_SUCCESS)
                ExitVMFaultQueryChild("wait on vmfault event failed",
                                      1, PollFd, &queue1, &queue2,
                                      vmFaultEvent, defaultGPUNode);

            // poll, read and query on pending vmfault event
            if (poll(fds, 1, 5000) <= 0)
                ExitVMFaultQueryChild("poll wake on vmfault event failed",
                                      1, PollFd, &queue1, &queue2,
                                      vmFaultEvent, defaultGPUNode);

            childStatus = read(PollFd, fifoBuf, kMaxSize) == -1
                          || strchr(fifoBuf, 'v') == NULL
                          || strchr(fifoBuf, 't');

            if (childStatus)
                ExitVMFaultQueryChild("read on vmfault event failed",
                                      1, PollFd, &queue1, &queue2,
                                      vmFaultEvent, defaultGPUNode);

            ret = hsaKmtQueryDebugEvent(defaultGPUNode, INVALID_PID,
                                                &qid1, true, &EventReceived,
                                                &IsSuspended, &IsNew);

            childStatus = ret != HSAKMT_STATUS_SUCCESS
                                 || EventReceived !=
                                     HSA_DEBUG_EVENT_TYPE_TRAP_VMFAULT;
            if (childStatus)
                ExitVMFaultQueryChild("query on vmfault event failed",
                                      1, PollFd, &queue1, &queue2,
                                      vmFaultEvent, defaultGPUNode);

            ExitVMFaultQueryChild("", 0, PollFd, &queue1, &queue2,
                                  vmFaultEvent, defaultGPUNode);

        } else {
            int childStatus;
            ASSERT_EQ(childPid, waitpid(childPid, &childStatus, 0));
            ASSERT_NE(0, WIFEXITED(childStatus));
            ASSERT_EQ(0, WEXITSTATUS(childStatus));
        }
    } else {
        LOG() << "Skipping test: Test not supported on family ID 0x"
              << m_FamilyId << "." << std::endl;
    }
    TEST_END
}
