/* * Copyright (C) 2020 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 "KFDSVMRangeTest.hpp" #include #include #include #include "PM4Queue.hpp" #include "PM4Packet.hpp" #include "SDMAPacket.hpp" #include "SDMAQueue.hpp" #include "Dispatch.hpp" void KFDSVMRangeTest::SetUp() { ROUTINE_START KFDBaseComponentTest::SetUp(); SVMSetXNACKMode(GetParam()); ROUTINE_END } void KFDSVMRangeTest::TearDown() { ROUTINE_START SVMRestoreXNACKMode(); KFDBaseComponentTest::TearDown(); ROUTINE_END } TEST_P(KFDSVMRangeTest, BasicSystemMemTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; PM4Queue queue; HSAuint64 AlternateVAGPU; unsigned int BufferSize = PAGE_SIZE; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } HsaMemoryBuffer isaBuffer(PAGE_SIZE, defaultGPUNode); HsaSVMRange srcSysBuffer(BufferSize, defaultGPUNode); HsaSVMRange destSysBuffer(BufferSize, defaultGPUNode); srcSysBuffer.Fill(0x01010101); ASSERT_SUCCESS(m_pAsm->RunAssembleBuf(CopyDwordIsa, isaBuffer.As())); ASSERT_SUCCESS(queue.Create(defaultGPUNode)); queue.SetSkipWaitConsump(0); Dispatch dispatch(isaBuffer); dispatch.SetArgs(srcSysBuffer.As(), destSysBuffer.As()); dispatch.Submit(queue); dispatch.Sync(g_TestTimeOut); EXPECT_SUCCESS(queue.Destroy()); EXPECT_EQ(destSysBuffer.As()[0], 0x01010101); TEST_END } TEST_P(KFDSVMRangeTest, SetGetAttributesTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL) if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } int i; unsigned int BufSize = PAGE_SIZE; HsaSVMRange *sysBuffer = new HsaSVMRange(BufSize); HSAuint32 nAttributes = 5; HSA_SVM_ATTRIBUTE outputAttributes[nAttributes]; HSA_SVM_ATTRIBUTE inputAttributes[] = { {HSA_SVM_ATTR_PREFETCH_LOC, (HSAuint32)defaultGPUNode}, {HSA_SVM_ATTR_PREFERRED_LOC, (HSAuint32)defaultGPUNode}, {HSA_SVM_ATTR_SET_FLAGS, HSA_SVM_FLAG_HOST_ACCESS | HSA_SVM_FLAG_GPU_EXEC | HSA_SVM_FLAG_COHERENT}, {HSA_SVM_ATTR_GRANULARITY, 0x3F}, {HSA_SVM_ATTR_ACCESS, (HSAuint32)defaultGPUNode}, }; HSAuint32 expectedDefaultResults[] = { INVALID_NODEID, INVALID_NODEID, HSA_SVM_FLAG_HOST_ACCESS | HSA_SVM_FLAG_COHERENT, 9, 0, }; HSAint32 enable = -1; EXPECT_SUCCESS(hsaKmtGetXNACKMode(&enable)); expectedDefaultResults[4] = (enable) ? HSA_SVM_ATTR_ACCESS : HSA_SVM_ATTR_NO_ACCESS; char *pBuf = sysBuffer->As(); LOG() << "Get default atrributes" << std::endl; memcpy(outputAttributes, inputAttributes, nAttributes * sizeof(HSA_SVM_ATTRIBUTE)); EXPECT_SUCCESS(hsaKmtSVMGetAttr(pBuf, BufSize, nAttributes, outputAttributes)); for (i = 0; i < nAttributes; i++) { if (outputAttributes[i].type == HSA_SVM_ATTR_ACCESS || outputAttributes[i].type == HSA_SVM_ATTR_ACCESS_IN_PLACE || outputAttributes[i].type == HSA_SVM_ATTR_NO_ACCESS) EXPECT_EQ(outputAttributes[i].type, expectedDefaultResults[i]); else EXPECT_EQ(outputAttributes[i].value, expectedDefaultResults[i]); } LOG() << "Setting/Getting atrributes" << std::endl; memcpy(outputAttributes, inputAttributes, nAttributes * sizeof(HSA_SVM_ATTRIBUTE)); EXPECT_SUCCESS(hsaKmtSVMSetAttr(pBuf, BufSize, nAttributes, inputAttributes)); EXPECT_SUCCESS(hsaKmtSVMGetAttr(pBuf, BufSize, nAttributes, outputAttributes)); for (i = 0; i < nAttributes; i++) { if (outputAttributes[i].type == HSA_SVM_ATTR_ACCESS || outputAttributes[i].type == HSA_SVM_ATTR_ACCESS_IN_PLACE || outputAttributes[i].type == HSA_SVM_ATTR_NO_ACCESS) EXPECT_EQ(inputAttributes[i].type, outputAttributes[i].type); else EXPECT_EQ(inputAttributes[i].value, outputAttributes[i].value); } delete sysBuffer; TEST_END } TEST_P(KFDSVMRangeTest, XNACKModeTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; HSAuint32 i, j; HSAint32 r; PM4Queue queue; HSAint32 enable = 0; const std::vector gpuNodes = m_NodeInfo.GetNodesWithGPU(); EXPECT_SUCCESS(hsaKmtGetXNACKMode(&enable)); for (i = 0; i < 2; i++) { enable = !enable; r = hsaKmtSetXNACKMode(enable); if (r == HSAKMT_STATUS_SUCCESS) { LOG() << "XNACK mode: " << std::boolalpha << enable << " supported" << std::endl; for (j = 0; j < gpuNodes.size(); j++) { LOG() << "Creating queue and try to set xnack mode on node: " << gpuNodes.at(j) << std::endl; ASSERT_SUCCESS(queue.Create(gpuNodes.at(j))); EXPECT_EQ(HSAKMT_STATUS_ERROR, hsaKmtSetXNACKMode(enable)); EXPECT_SUCCESS(queue.Destroy()); } } else if (r == HSAKMT_STATUS_NOT_SUPPORTED) { LOG() << "XNACK mode: " << std::boolalpha << enable << " NOT supported" << std::endl; } } TEST_END } TEST_P(KFDSVMRangeTest, InvalidRangeTest) { TEST_START(TESTPROFILE_RUNALL) if (!SVMAPISupported()) return; HSAuint32 Flags;; HSAKMT_STATUS ret; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; Flags = HSA_SVM_FLAG_HOST_ACCESS | HSA_SVM_FLAG_COHERENT; ret = RegisterSVMRange(defaultGPUNode, reinterpret_cast(0x10000), 0x1000, 0, Flags); EXPECT_NE(ret, HSAKMT_STATUS_SUCCESS); TEST_END } void KFDSVMRangeTest::SplitRangeTest(int defaultGPUNode, int prefetch_location) { unsigned int BufSize = 16 * PAGE_SIZE; if (!SVMAPISupported()) return; HsaSVMRange *sysBuffer; HsaSVMRange *sysBuffer2; HsaSVMRange *sysBuffer3; HsaSVMRange *sysBuffer4; void *pBuf; // case 1 pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(reinterpret_cast(pBuf) + 8192, PAGE_SIZE, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer; munmap(pBuf, BufSize); // case 2.1 pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(reinterpret_cast(pBuf) + 4096, BufSize - 4096, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer; munmap(pBuf, BufSize); // case 2.2 pBuf = mmap(0, BufSize + 8192, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(reinterpret_cast(pBuf) + 8192, BufSize, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer; munmap(pBuf, BufSize + 8192); // case 3 pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(reinterpret_cast(pBuf), BufSize - 8192, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer; munmap(pBuf, BufSize); // case 4.1 pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer; munmap(pBuf, BufSize); // case 4.2 pBuf = mmap(0, BufSize + 8192, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(pBuf, BufSize + 8192, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer; munmap(pBuf, BufSize + 8192); // case 5 pBuf = mmap(0, BufSize + 65536, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(reinterpret_cast(pBuf) + 8192, 8192, defaultGPUNode, prefetch_location); sysBuffer2 = new HsaSVMRange(reinterpret_cast(pBuf) + 32768, 8192, defaultGPUNode, prefetch_location); sysBuffer3 = new HsaSVMRange(pBuf, BufSize + 65536, defaultGPUNode, prefetch_location); delete sysBuffer2; delete sysBuffer3; delete sysBuffer; munmap(pBuf, BufSize + 65536); // case 6, unregister after free pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(reinterpret_cast(pBuf) + 8192, 8192, defaultGPUNode, prefetch_location); munmap(pBuf, BufSize); delete sysBuffer; } TEST_P(KFDSVMRangeTest, SplitSystemRangeTest) { const HsaNodeProperties *pNodeProperties = m_NodeInfo.HsaDefaultGPUNodeProperties(); TEST_START(TESTPROFILE_RUNALL) if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } SplitRangeTest(defaultGPUNode, 0); TEST_END } TEST_P(KFDSVMRangeTest, EvictSystemRangeTest) { const HsaNodeProperties *pNodeProperties = m_NodeInfo.HsaDefaultGPUNodeProperties(); TEST_START(TESTPROFILE_RUNALL) if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } HSAuint32 stackData[2 * PAGE_SIZE] = {0}; char *pBuf = reinterpret_cast(((uint64_t)stackData + PAGE_SIZE) & ~(PAGE_SIZE - 1)); HSAuint32 *globalData = reinterpret_cast(pBuf); const unsigned dstOffset = ((uint64_t)pBuf + 2 * PAGE_SIZE - (uint64_t)stackData) / 4; const unsigned sdmaOffset = dstOffset + PAGE_SIZE; *globalData = 0xdeadbeef; HsaSVMRange srcBuffer((globalData), PAGE_SIZE, defaultGPUNode); HsaSVMRange dstBuffer(&stackData[dstOffset], PAGE_SIZE, defaultGPUNode); HsaSVMRange sdmaBuffer(&stackData[sdmaOffset], PAGE_SIZE, defaultGPUNode); /* Create PM4 and SDMA queues before fork+COW to test queue * eviction and restore */ PM4Queue pm4Queue; SDMAQueue sdmaQueue; ASSERT_SUCCESS(pm4Queue.Create(defaultGPUNode)); ASSERT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); HsaMemoryBuffer isaBuffer(PAGE_SIZE, defaultGPUNode, true/*zero*/, false/*local*/, true/*exec*/); ASSERT_SUCCESS(m_pAsm->RunAssembleBuf(CopyDwordIsa, isaBuffer.As())); Dispatch dispatch0(isaBuffer); dispatch0.SetArgs(srcBuffer.As(), dstBuffer.As()); dispatch0.Submit(pm4Queue); dispatch0.Sync(g_TestTimeOut); sdmaQueue.PlaceAndSubmitPacket(SDMAWriteDataPacket(sdmaQueue.GetFamilyId(), sdmaBuffer.As(), 0x12345678)); sdmaQueue.Wait4PacketConsumption(); EXPECT_TRUE(WaitOnValue(&stackData[sdmaOffset], 0x12345678)); /* Fork a child process to mark pages as COW */ pid_t pid = fork(); ASSERT_GE(pid, 0); if (pid == 0) { /* Child process waits for a SIGTERM from the parent. It can't * make any write access to the stack because we want the * parent to make the first write access and get a new copy. A * busy loop is the safest way to do that, since any function * call (e.g. sleep) would write to the stack. */ while (1) {} WARN() << "Shouldn't get here!" << std::endl; exit(0); } /* Parent process writes to COW page(s) and gets a new copy. MMU * notifier needs to update the GPU mapping(s) for the test to * pass. */ *globalData = 0xD00BED00; stackData[dstOffset] = 0xdeadbeef; stackData[sdmaOffset] = 0xdeadbeef; /* Terminate the child process before a possible test failure that * would leave it spinning in the background indefinitely. */ int status; EXPECT_EQ(0, kill(pid, SIGTERM)); EXPECT_EQ(pid, waitpid(pid, &status, 0)); EXPECT_NE(0, WIFSIGNALED(status)); EXPECT_EQ(SIGTERM, WTERMSIG(status)); /* Now check that the GPU is accessing the correct page */ Dispatch dispatch1(isaBuffer); dispatch1.SetArgs(srcBuffer.As(), dstBuffer.As()); dispatch1.Submit(pm4Queue); dispatch1.Sync(g_TestTimeOut); sdmaQueue.PlaceAndSubmitPacket(SDMAWriteDataPacket(sdmaQueue.GetFamilyId(), sdmaBuffer.As(), 0xD0BED0BE)); sdmaQueue.Wait4PacketConsumption(); EXPECT_SUCCESS(pm4Queue.Destroy()); EXPECT_SUCCESS(sdmaQueue.Destroy()); EXPECT_EQ(0xD00BED00, *globalData); EXPECT_EQ(0xD00BED00, stackData[dstOffset]); EXPECT_EQ(0xD0BED0BE, stackData[sdmaOffset]); TEST_END } TEST_P(KFDSVMRangeTest, PartialUnmapSysMemTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; unsigned int BufSize = 16 * PAGE_SIZE; void *pBuf; PM4Queue queue; HsaMemoryBuffer isaBuffer(PAGE_SIZE, defaultGPUNode); HsaSVMRange *sysBuffer; HsaSVMRange destSysBuffer(BufSize, defaultGPUNode); pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); sysBuffer = new HsaSVMRange(pBuf, BufSize, defaultGPUNode, 0); sysBuffer->Fill(0x01010101); char *pBuf2 = reinterpret_cast(pBuf) + 8192; unsigned int Buf2Size = 4 * PAGE_SIZE; char *pBuf3 = pBuf2 + Buf2Size; munmap(pBuf2, Buf2Size); ASSERT_SUCCESS(m_pAsm->RunAssembleBuf(CopyDwordIsa, isaBuffer.As())); ASSERT_SUCCESS(queue.Create(defaultGPUNode)); Dispatch dispatch(isaBuffer); Dispatch dispatch2(isaBuffer); dispatch.SetArgs(pBuf3, destSysBuffer.As()); dispatch.Submit(queue); dispatch.Sync(g_TestTimeOut); EXPECT_EQ(destSysBuffer.As()[0], 0x01010101); dispatch2.SetArgs(pBuf, destSysBuffer.As()); dispatch2.Submit(queue); dispatch2.Sync(g_TestTimeOut); EXPECT_EQ(destSysBuffer.As()[0], 0x01010101); EXPECT_SUCCESS(queue.Destroy()); munmap(pBuf, BufSize); TEST_END } TEST_P(KFDSVMRangeTest, BasicVramTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; PM4Queue queue; HSAuint64 AlternateVAGPU; unsigned int BufferSize = PAGE_SIZE; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } HsaMemoryBuffer isaBuffer(PAGE_SIZE, defaultGPUNode); HsaSVMRange srcSysBuffer(BufferSize, defaultGPUNode); HsaSVMRange locBuffer(BufferSize, defaultGPUNode, defaultGPUNode); HsaSVMRange destSysBuffer(BufferSize, defaultGPUNode); srcSysBuffer.Fill(0x01010101); ASSERT_SUCCESS(m_pAsm->RunAssembleBuf(CopyDwordIsa, isaBuffer.As())); ASSERT_SUCCESS(queue.Create(defaultGPUNode)); queue.SetSkipWaitConsump(0); Dispatch dispatch(isaBuffer); Dispatch dispatch2(isaBuffer); dispatch.SetArgs(srcSysBuffer.As(), locBuffer.As()); dispatch.Submit(queue); dispatch.Sync(g_TestTimeOut); dispatch2.SetArgs(locBuffer.As(), destSysBuffer.As()); dispatch2.Submit(queue); dispatch2.Sync(g_TestTimeOut); EXPECT_SUCCESS(queue.Destroy()); EXPECT_EQ(destSysBuffer.As()[0], 0x01010101); TEST_END } TEST_P(KFDSVMRangeTest, SplitVramRangeTest) { TEST_START(TESTPROFILE_RUNALL) if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } SplitRangeTest(defaultGPUNode, defaultGPUNode); TEST_END } TEST_P(KFDSVMRangeTest, PrefetchTest) { TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; unsigned int BufSize = 16 << 10; HsaSVMRange *sysBuffer; uint32_t node_id; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; sysBuffer = new HsaSVMRange(BufSize, defaultGPUNode); char *pBuf = sysBuffer->As(); /* Using invalid svm range to get prefetch node should return failed */ delete sysBuffer; EXPECT_SUCCESS(!SVMRangeGetPrefetchNode(pBuf, BufSize, &node_id)); sysBuffer = new HsaSVMRange(BufSize, defaultGPUNode); pBuf = sysBuffer->As(); char *pLocBuf = pBuf + BufSize / 2; EXPECT_SUCCESS(SVMRangeGetPrefetchNode(pBuf, BufSize, &node_id)); EXPECT_EQ(node_id, 0); EXPECT_SUCCESS(SVMRangePrefetchToNode(pLocBuf, BufSize / 2, defaultGPUNode)); EXPECT_SUCCESS(SVMRangeGetPrefetchNode(pLocBuf, BufSize / 2, &node_id)); EXPECT_EQ(node_id, defaultGPUNode); EXPECT_SUCCESS(SVMRangeGetPrefetchNode(pBuf, BufSize, &node_id)); EXPECT_EQ(node_id, 0xffffffff); delete sysBuffer; TEST_END } TEST_P(KFDSVMRangeTest, MigrateTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } HSAuint32 migrateRepeat = 8; unsigned int BufferSize = 16 << 20; HsaSVMRange DataBuffer(BufferSize, defaultGPUNode); HSAuint32 *pData = DataBuffer.As(); HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); HSAuint32 *pBuf = SysBuffer.As(); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, 0)); HsaSVMRange SysBuffer2(BufferSize, defaultGPUNode); HSAuint32 *pBuf2 = SysBuffer2.As(); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf2, BufferSize, 0)); SDMAQueue sdmaQueue; ASSERT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); for (HSAuint32 i = 0; i < BufferSize / 4; i++) pData[i] = i; while (migrateRepeat--) { /* Migrate from ram to vram */ EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, defaultGPUNode)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf2, BufferSize, defaultGPUNode)); /* Update content in migrated buffer in vram */ sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pBuf, pData, BufferSize)); sdmaQueue.Wait4PacketConsumption(); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pBuf2, pData, BufferSize)); sdmaQueue.Wait4PacketConsumption(); /* Migrate from vram to ram * CPU access the buffer migrated to vram have page fault * page fault trigger migration from vram back to ram * so SysBuffer should have same value as in vram */ for (HSAuint32 i = 0; i < BufferSize / 4; i++) { ASSERT_EQ(i, pBuf[i]); ASSERT_EQ(i, pBuf2[i]); } } /* If xnack off, after migrating back to ram, GPU mapping should be updated to ram * test if shade can read from ram * If xnack on, GPU mapping should be cleared, test if GPU vm fault can update * page table and shade can read from ram. */ sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pBuf, pData, BufferSize)); sdmaQueue.Wait4PacketConsumption(); for (HSAuint32 i = 0; i < BufferSize / 4; i++) ASSERT_EQ(i, pBuf[i]); TEST_END } /* * Test if GPU mapping to system memory is correct after range on VRAM split and migrate back * to system memory. * * Steps, it is same for XNACK on or off * 1. alloc 256MB range on system memory, set ACCESS_IN_PLACE by GPU * 2. Prefetcg to migrate range to GPU VRAM * 3. Use CPU to fill the range, range is migrated back to system memory, and split by granularity, * GPU mapping update to system memory * 4. Use GPU sdma to fill the range in system memory * 5. Check if data is correct in system memory */ TEST_P(KFDSVMRangeTest, MigrateAccessInPlaceTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } unsigned int BufferSize = MIN(256ULL << 20, GetVramSize(defaultGPUNode) / 2); SDMAQueue sdmaQueue; ASSERT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); HsaSVMRange DataBuffer(BufferSize, defaultGPUNode); HSAuint32 *pData = DataBuffer.As(); EXPECT_SUCCESS(SVMRangeMapInPlaceToNode(pData, BufferSize, defaultGPUNode)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pData, BufferSize, defaultGPUNode)); for (HSAuint32 i = 0; i < BufferSize / 4; i += 1024) pData[i] = i; /* GPU/SDMA update content in buffer migrated back to system memory */ sdmaQueue.PlaceAndSubmitPacket(SDMAFillDataPacket(sdmaQueue.GetFamilyId(), pData, 0x55AAAA55, BufferSize)); sdmaQueue.Wait4PacketConsumption(); for (HSAuint32 i = 0; i < BufferSize / 4; i += 1024) ASSERT_EQ(0x55AAAA55, pData[i]); ASSERT_SUCCESS(sdmaQueue.Destroy()); TEST_END } /* * The test changes migration granularity, then trigger CPU page fault to migrate * the svm range from vram to ram. * Check the dmesg driver output to confirm the number of CPU page fault is correct * based on granularity. * * For example, this is BufferPages = 5, while granularity change from 2 to 0 * [ 292.623498] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597ee000 * [ 292.623727] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597f0000 * [ 292.724414] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597ee000 * [ 292.724824] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597f0000 * [ 292.725094] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597f2000 * [ 292.728186] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597ee000 * [ 292.729171] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597ef000 * [ 292.729576] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597f0000 * [ 292.730010] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597f1000 * [ 292.730931] amdgpu:svm_migrate_to_ram:744: CPU page fault address 0x7f22597f2000 */ TEST_P(KFDSVMRangeTest, MigrateGranularityTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } HSAuint64 BufferPages = 16384; HSAuint64 BufferSize = BufferPages * PAGE_SIZE; HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); HSAint32 *pBuf = SysBuffer.As(); HsaSVMRange SysBuffer2(BufferSize, defaultGPUNode); HSAint32 *pBuf2 = SysBuffer2.As(); HSAint32 Granularity; SDMAQueue sdmaQueue; ASSERT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); for (Granularity = 0; (1ULL << Granularity) <= BufferPages; Granularity++); for (HSAuint32 i = 0; i < BufferPages; i++) pBuf2[i * PAGE_SIZE / 4] = i; while (Granularity--) { /* Prefetch the entire range to vram */ EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, defaultGPUNode)); EXPECT_SUCCESS(SVMRangSetGranularity(pBuf, BufferSize, Granularity)); /* Change Buffer content in vram, then migrate it back to ram */ sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pBuf, pBuf2, BufferSize)); sdmaQueue.Wait4PacketConsumption(); /* Migrate from vram to ram */ for (HSAuint32 i = 0; i < BufferPages; i++) ASSERT_EQ(i, pBuf[i * PAGE_SIZE / 4]); } TEST_END } TEST_P(KFDSVMRangeTest, MigrateLargeBufTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; PM4Queue queue; HSAuint64 AlternateVAGPU; unsigned long BufferSize = 1L << 30; unsigned long maxSDMASize = 128L << 20; /* IB size is 4K */ unsigned long Size, i; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; HSAuint64 vramSize; vramSize = GetVramSize(defaultGPUNode); if (!vramSize) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } BufferSize = MIN(BufferSize, vramSize * 3 / 4); HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); SysBuffer.Fill(0x1); HsaSVMRange SysBuffer2(BufferSize, defaultGPUNode); SysBuffer2.Fill(0x2); /* Migrate from ram to vram * using same address to register to GPU to trigger migration * so LocalBuffer will have same value as SysBuffer */ HsaSVMRange LocalBuffer(SysBuffer.As(), BufferSize, defaultGPUNode, defaultGPUNode); SDMAQueue sdmaQueue; ASSERT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); for (i = 0; i < BufferSize; i += Size) { Size = (BufferSize - i) > maxSDMASize ? maxSDMASize : (BufferSize - i); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), SysBuffer2.As() + i, LocalBuffer.As() + i, Size)); sdmaQueue.Wait4PacketConsumption(); } /* Check content in migrated buffer in vram */ for (i = 0; i < BufferSize / 4; i += 1024) ASSERT_EQ(0x1, SysBuffer2.As()[i]); /* Change LocalBuffer content in vram, then migrate it back to ram */ SysBuffer2.Fill(0x3); for (i = 0; i < BufferSize; i += Size) { Size = (BufferSize - i) > maxSDMASize ? maxSDMASize : (BufferSize - i); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), LocalBuffer.As() + i, SysBuffer2.As() + i, Size)); sdmaQueue.Wait4PacketConsumption(); } /* Migrate from vram to ram * CPU access the buffer migrated to vram have page fault * page fault trigger migration from vram back to ram * so SysBuffer should have same value as in LocalBuffer */ EXPECT_SUCCESS(SVMRangSetGranularity(SysBuffer.As(), BufferSize, 30)); for (i = 0; i < BufferSize / 4; i += 1024) ASSERT_EQ(0x3, SysBuffer.As()[i]); /* After migrating back to ram, GPU mapping should be updated to ram * test if shade can read from ram */ SysBuffer.Fill(0x4); for (i = 0; i < BufferSize; i += Size) { Size = (BufferSize - i) > maxSDMASize ? maxSDMASize : (BufferSize - i); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), SysBuffer2.As() + i, LocalBuffer.As() + i, Size)); sdmaQueue.Wait4PacketConsumption(); } for (i = 0; i < BufferSize / 4; i += 1024) ASSERT_EQ(0x4, SysBuffer2.As()[i]); TEST_END } TEST_P(KFDSVMRangeTest, MigratePolicyTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } unsigned long BufferSize = 1UL << 20; HsaSVMRange DataBuffer(BufferSize, defaultGPUNode); HSAuint64 *pData = DataBuffer.As(); HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); HSAuint64 *pBuf = SysBuffer.As(); SDMAQueue sdmaQueue; ASSERT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); for (HSAuint64 i = 0; i < BufferSize / 8; i++) pData[i] = i; /* Prefetch to migrate from ram to vram */ EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, defaultGPUNode)); /* Update content in migrated buffer in vram */ sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pBuf, pData, BufferSize)); sdmaQueue.Wait4PacketConsumption(NULL, HSA_EVENTTIMEOUT_INFINITE); /* Migrate from vram to ram * CPU access the buffer migrated to vram have page fault * page fault trigger migration from vram back to ram * so SysBuffer should have same value as in vram */ for (HSAuint64 i = 0; i < BufferSize / 8; i++) { ASSERT_EQ(i, pBuf[i]); /* Update buf */ pBuf[i] = i + 1; } /* Migrate from ram to vram if xnack on * If xnack off, after migrating back to ram, GPU mapping should be updated to ram * test if shade can read from ram * If xnack on, GPU mapping should be cleared, test if GPU vm fault can update * page table and shade can read from ram. */ //#define USE_PM4_QUEUE_TRIGGER_VM_FAULT #ifdef USE_PM4_QUEUE_TRIGGER_VM_FAULT HsaMemoryBuffer isaBuffer(PAGE_SIZE, defaultGPUNode); PM4Queue queue; ASSERT_SUCCESS(m_pAsm->RunAssembleBuf(CopyDwordIsa, isaBuffer.As())); ASSERT_SUCCESS(queue.Create(defaultGPUNode)); for (HSAuint64 i = 0; i < BufferSize / 8; i += 512) { Dispatch dispatch(isaBuffer); dispatch.SetArgs(pBuf + i, pData + i); dispatch.Submit(queue); dispatch.Sync(HSA_EVENTTIMEOUT_INFINITE); } #else sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pData, pBuf, BufferSize)); sdmaQueue.Wait4PacketConsumption(NULL, HSA_EVENTTIMEOUT_INFINITE); #endif for (HSAuint64 i = 0; i < BufferSize / 8; i += 512) ASSERT_EQ(i + 1, pData[i]); ASSERT_SUCCESS(sdmaQueue.Destroy()); TEST_END } /* Multiple GPU migration test * * Steps: * 1. Prefetch pBuf, pData to all GPUs, to test migration from GPU to GPU * 2. Use sdma queue on all GPUs, to copy data from pBuf to pData * 3. Check pData data * * Notes: * With xnack on, step 2 will have retry fault on pBuf, to migrate from GPU to GPU, * retry fault on pData, to migrate from CPU to GPU * * With xnack off, pBuf and pData should prefetch to CPU to ensure multiple GPU access * * step3 migrate pData from GPU to CPU * * Test will skip if only one GPU found */ TEST_P(KFDSVMRangeTest, MultiGPUMigrationTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } const std::vector gpuNodesAll = m_NodeInfo.GetNodesWithGPU(); std::vector gpuNodes; for (auto node : gpuNodesAll) { const HsaNodeProperties *pNodeProperties; pNodeProperties = m_NodeInfo.GetNodeProperties(node); if (pNodeProperties->Capability.ui32.SVMAPISupported) gpuNodes.push_back(node); } if (gpuNodes.size() < 2) { LOG() << "Skipping test: at least two SVM supported GPUs needed." << std::endl; return; } unsigned long BufferSize = 1UL << 20; HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); HSAuint64 *pBuf = SysBuffer.As(); HsaSVMRange DataBuffer(BufferSize, defaultGPUNode); HSAuint64 *pData = DataBuffer.As(); SDMAQueue sdmaQueue; for (HSAuint64 i = 0; i < BufferSize / 8; i++) pBuf[i] = i; for (auto node : gpuNodes) { EXPECT_SUCCESS(SVMRangeMapToNode(pBuf, BufferSize, node)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, node)); EXPECT_SUCCESS(SVMRangeMapToNode(pData, BufferSize, node)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pData, BufferSize, node)); } for (auto node : gpuNodes) { ASSERT_SUCCESS(sdmaQueue.Create(node)); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pData, pBuf, BufferSize)); sdmaQueue.Wait4PacketConsumption(); for (HSAuint64 i = 0; i < BufferSize / 8; i += 512) ASSERT_EQ(i, pData[i]); EXPECT_SUCCESS(sdmaQueue.Destroy()); } TEST_END } /* Multiple GPU access in place test * * Steps: * 1. Prefetch pBuf, pData to all GPUs, with ACCESS_IN_PLACE on GPUs * 2. Use sdma queue on all GPUs, to copy data from pBuf to pData * 3. Prefetch pData to CPU, check pData data * * Notes: * With xnack on, step 2 will have retry fault on pBuf, to migrate from GPU to GPU. * If multiple GPU on xGMI same hive, there should not have retry fault on pBuf * because mapping should update to another GPU vram through xGMI * * With xnack off, pBuf and pData should prefetch to CPU to ensure multiple GPU access * * step3 migrate pData from GPU to CPU, should not have retry fault on GPUs. * * Test will skip if only one GPU found */ TEST_P(KFDSVMRangeTest, MultiGPUAccessInPlaceTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } const std::vector gpuNodesAll = m_NodeInfo.GetNodesWithGPU(); std::vector gpuNodes; for (auto node : gpuNodesAll) { const HsaNodeProperties *pNodeProperties; pNodeProperties = m_NodeInfo.GetNodeProperties(node); if (pNodeProperties->Capability.ui32.SVMAPISupported) gpuNodes.push_back(node); } if (gpuNodes.size() < 2) { LOG() << "Skipping test: at least two SVM supported GPUs needed." << std::endl; return; } unsigned long BufferSize = 1UL << 20; HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); HSAuint64 *pBuf = SysBuffer.As(); HsaSVMRange DataBuffer(BufferSize, defaultGPUNode); HSAuint64 *pData = DataBuffer.As(); SDMAQueue sdmaQueue; for (HSAuint64 i = 0; i < BufferSize / 8; i++) pBuf[i] = i; for (auto node : gpuNodes) { EXPECT_SUCCESS(SVMRangeMapInPlaceToNode(pBuf, BufferSize, node)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, node)); EXPECT_SUCCESS(SVMRangeMapInPlaceToNode(pData, BufferSize, node)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pData, BufferSize, node)); } for (auto node : gpuNodes) { ASSERT_SUCCESS(sdmaQueue.Create(node)); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pData, pBuf, BufferSize)); sdmaQueue.Wait4PacketConsumption(); for (HSAuint64 i = 0; i < BufferSize / 8; i += 512) ASSERT_EQ(i, pData[i]); EXPECT_SUCCESS(sdmaQueue.Destroy()); } TEST_END } /* Multiple thread migration test * * 2 threads do migration at same time to test range migration race conditon handle. * * Steps: * 1. register 128MB range on system memory, don't map to GPU, 128MB is max size to put in * sdma queue 4KB IB buffer. * 2. one thread prefetch range to GPU, another thread use sdma queue to access range at same * time to generate retry vm fault to migrate range to GPU * 3. one thread prefetch range to CPU, another thread read range to generate CPU page fault * to migrate range to CPU at same time * 4. loop test step 2 and 3 twice, to random CPU/GPU fault and prefetch migration order */ struct ReadThreadParams { HSAuint64* pBuf; HSAint64 BufferSize; int defaultGPUNode; }; unsigned int CpuReadThread(void* p) { struct ReadThreadParams* pArgs = reinterpret_cast(p); for (HSAuint64 i = 0; i < pArgs->BufferSize / 8; i += 512) EXPECT_EQ(i, pArgs->pBuf[i]); return 0; } unsigned int GpuReadThread(void* p) { struct ReadThreadParams* pArgs = reinterpret_cast(p); EXPECT_SUCCESS(SVMRangePrefetchToNode(pArgs->pBuf, pArgs->BufferSize, pArgs->defaultGPUNode)); return 0; } TEST_P(KFDSVMRangeTest, MultiThreadMigrationTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } unsigned long test_loops = 2; unsigned long BufferSize = 1UL << 27; HsaSVMRange SysBuffer(BufferSize, defaultGPUNode); HSAuint64 *pBuf = SysBuffer.As(); HsaSVMRange DataBuffer(BufferSize, defaultGPUNode); HSAuint64 *pData = DataBuffer.As(); SDMAQueue sdmaQueue; uint64_t threadId; struct ReadThreadParams params; params.pBuf = pBuf; params.BufferSize = BufferSize; params.defaultGPUNode = defaultGPUNode; EXPECT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); for (HSAuint64 i = 0; i < BufferSize / 8; i++) pBuf[i] = i; for (HSAuint64 i = 0; i < test_loops; i++) { /* 2 threads migrate to GPU */ sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pData, pBuf, BufferSize)); ASSERT_EQ(true, StartThread(&GpuReadThread, ¶ms, threadId)); sdmaQueue.Wait4PacketConsumption(); WaitForThread(threadId); /* 2 threads migrate to cpu */ ASSERT_EQ(true, StartThread(&CpuReadThread, ¶ms, threadId)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufferSize, 0)); WaitForThread(threadId); } EXPECT_SUCCESS(sdmaQueue.Destroy()); TEST_END } /* * Test SVM support file backed range * * Create temp file, mmap to alloc memory backed on file. * Create file backed svm range, to map to GPU for xnack on or off * Use sdma to write data to memory, should write to file * Close file, and then check if file data is updated correctly */ TEST_P(KFDSVMRangeTest, MigrateFileBackedRangeTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } char tmpfname[] = "/tmp/kfdtest-XXXXXX"; int fd = mkostemp(tmpfname, 0600); ASSERT_NE(-1, fd); size_t size = PAGE_SIZE; char *buf = reinterpret_cast(alloca(size)); memset(buf, 0x30, size); ASSERT_EQ(size, write(fd, buf, size)); void *MmapedFile = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); ASSERT_NE(MAP_FAILED, MmapedFile); HsaSVMRange filebackedRange(MmapedFile, size, defaultGPUNode, defaultGPUNode); SDMAQueue sdmaQueue; EXPECT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); sdmaQueue.PlaceAndSubmitPacket(SDMAFillDataPacket(sdmaQueue.GetFamilyId(), MmapedFile, 0x33333333, size)); sdmaQueue.Wait4PacketConsumption(); EXPECT_SUCCESS(sdmaQueue.Destroy()); munmap(MmapedFile, size); EXPECT_SUCCESS(close(fd)); fd = open(tmpfname, O_RDONLY); ASSERT_NE(-1, fd); ASSERT_EQ(size, read(fd, buf, size)); EXPECT_EQ(0x33, buf[0]); EXPECT_SUCCESS(close(fd)); EXPECT_SUCCESS(remove(tmpfname)); TEST_END } /* * Test SVM support read only range * * Map read only range to GPU, test sdma can read the range * write to range should trigger GPU vm fault for both xnack on and off */ TEST_P(KFDSVMRangeTest, ReadOnlyRangeTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } /* * Use child process to run test because the test trigger GPU vm fault, KFD evict all user queues * of the process and no more test can run after vm fault on the process. */ int pid = fork(); if (pid == 0) { TearDown(); SetUp(); } else { int childStatus; waitpid(pid, &childStatus, 0); if (is_dgpu()) { EXPECT_EQ(true, WIFEXITED(childStatus)); EXPECT_EQ(0, WEXITSTATUS(childStatus)); } else { EXPECT_EQ(true, WIFSIGNALED(childStatus)); EXPECT_EQ(SIGSEGV, WTERMSIG(childStatus)); } return; } /* Use child process to run test */ int ret = 0; HsaSVMRange inBuffer(PAGE_SIZE * 2, defaultGPUNode); HSAuint8 *pinBuf = inBuffer.As(); memset(pinBuf, 0x55, PAGE_SIZE); /* Map readonly pinBuf to GPU, sDMA should be able to read it */ mprotect(pinBuf, PAGE_SIZE, PROT_READ); HsaSVMRange outputBuffer(PAGE_SIZE, defaultGPUNode); HSAuint8 *pBuf = outputBuffer.As(); HsaEvent *vmFaultEvent; HSAuint64 faultAddress; 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) { WARN() << "Event create failed" << std::endl; exit(ret); } SDMAQueue sdmaQueue; ret = sdmaQueue.Create(defaultGPUNode); if (ret != HSAKMT_STATUS_SUCCESS) { WARN() << "Queue create failed" << std::endl; goto queue_fail; } sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pBuf, reinterpret_cast(pinBuf), PAGE_SIZE)); sdmaQueue.Wait4PacketConsumption(); EXPECT_EQ(0x55, pBuf[0]); if (pBuf[0] != 0x55) goto event_fail; /* sDMA write to readonly pinBuf should fail with GPU vm fault, check if pinBuf content is * not changed, and KFD send HSA_EVENTTYPE_MEMORY event back with fault address pinBuf. * * This must be the last step of test because all queues are evicted after vm fault. */ memset(pBuf, 0xAA, PAGE_SIZE); sdmaQueue.PlaceAndSubmitPacket(SDMACopyDataPacket(sdmaQueue.GetFamilyId(), pinBuf, reinterpret_cast(pBuf), PAGE_SIZE)); ret = hsaKmtWaitOnEvent(vmFaultEvent, g_TestTimeOut); if (ret != HSAKMT_STATUS_SUCCESS) { WARN() << "Wait failed. No Exception triggered" << std::endl; goto event_fail; } if (vmFaultEvent->EventData.EventType != HSA_EVENTTYPE_MEMORY) { WARN() << "Unexpected Event Received " << vmFaultEvent->EventData.EventType << std::endl; ret = HSAKMT_STATUS_ERROR; goto event_fail; } faultAddress = vmFaultEvent->EventData.EventData.MemoryAccessFault.VirtualAddress; if (faultAddress != (HSAuint64)pinBuf) { WARN() << "Unexpected Fault Address " << faultAddress << std::endl; ret = HSAKMT_STATUS_ERROR; } event_fail: EXPECT_SUCCESS(sdmaQueue.Destroy()); queue_fail: hsaKmtDestroyEvent(vmFaultEvent); /* Child process exit, otherwise it will continue to run remaining tests */ exit(ret); TEST_END } /* * Test SMI HMM SVM profiling event * Use separate thread to read event the same way as ROCr and ROCProfiler */ struct ReadEventThreadParams { int nodeid; HSAuint64 *pBuf; int BufSize; pthread_barrier_t *barrier; }; unsigned int ReadSMIEventThread(void* p) { struct ReadEventThreadParams *pArgs = (struct ReadEventThreadParams *)p; char msg[HSA_SMI_EVENT_MSG_SIZE]; struct pollfd fds = {0}; HSAuint64 events; int fd; EXPECT_SUCCESS(hsaKmtOpenSMI(pArgs->nodeid, &fd)); events = HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_INDEX_MAX) - 1; EXPECT_EQ(write(fd, &events, sizeof(events)), sizeof(events)); pthread_barrier_wait(pArgs->barrier); fds.fd = fd; fds.events = POLLIN; EXPECT_GE(poll(&fds, 1, 1000), 0); memset(msg, 0, sizeof(msg)); EXPECT_GE(read(fd, msg, HSA_SMI_EVENT_MSG_SIZE), 0); int event_id, pid, size, trigger, unused; HSAuint64 timestamp; HSAuint64 addr; EXPECT_EQ(sscanf(msg, "%x %ld -%d @%lx(%d) %d->%x %x:%d %d\n", &event_id, ×tamp, &pid, &addr, &size, &unused, &unused, &unused, &unused, &trigger), 10); EXPECT_EQ(event_id, HSA_SMI_EVENT_MIGRATE_START); EXPECT_EQ((HSAuint64 *)(addr << PAGE_SHIFT), pArgs->pBuf); EXPECT_EQ(size << PAGE_SHIFT, pArgs->BufSize); EXPECT_EQ(pid, getpid()); EXPECT_EQ(trigger, HSA_MIGRATE_TRIGGER_PREFETCH); close(fd); return 0; } TEST_P(KFDSVMRangeTest, HMMProfilingEvent) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; if (m_VersionInfo.KernelInterfaceMinorVersion < 10) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (!GetVramSize(defaultGPUNode)) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } if (m_NodeInfo.IsAppAPU(defaultGPUNode)) { LOG() << "Skipping test on AppAPU." << std::endl; return; } pthread_barrier_t barrier; ASSERT_SUCCESS(pthread_barrier_init(&barrier, NULL, 2)); int BufSize = 16 << 10; HsaSVMRange SysBuffer(BufSize, defaultGPUNode); HSAuint64 *pBuf = SysBuffer.As(); struct ReadEventThreadParams pArgs = {defaultGPUNode, pBuf, BufSize, &barrier}; uint64_t threadId; ASSERT_EQ(true, StartThread(&ReadSMIEventThread, &pArgs, threadId)); pthread_barrier_wait(&barrier); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufSize, defaultGPUNode)); WaitForThread(threadId); TEST_END } /* * Test SVM support VRAM overcommitment * * Prefetch total VRAM size plus overCommitSize SVM range to VRAM. after VRAM is full, * KFD should support VRAM overcommitment by evicting SVM ranges to system memory to alloc * VRAM for new ranges. */ TEST_P(KFDSVMRangeTest, VramOvercommitTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } HSAuint64 vramSize = GetVramSize(defaultGPUNode); if (!vramSize) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } unsigned long overCommitSize = 1UL << 30; /* With XNACK off, KFD checks that all SVM memory will fit into system memory */ if (vramSize + overCommitSize > GetSysMemSize() / 2) { LOG() << "Skipping test: Not enough system memory." << std::endl; return; } unsigned long BufSize = 512UL << 20; unsigned long numBufs = (vramSize + overCommitSize) / BufSize; HSAKMT_STATUS ret; void *pBuf[numBufs]; unsigned long i; for (i = 0; i < numBufs; i++) { pBuf[i] = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, pBuf[i]); ret = RegisterSVMRange(defaultGPUNode, pBuf[i], BufSize, defaultGPUNode, 0); if (ret != HSAKMT_STATUS_SUCCESS) break; } EXPECT_EQ(numBufs, i); while (i--) munmap(pBuf[i], BufSize); TEST_END } /* * Test SVM support VRAM overcommitment * * Prefetch giant overcommit SVM range to VRAM, KFD should support VRAM overcommitment * by spliting giant range into smaller ranges, evicting SVM ranges to system memory to * alloc VRAM for overcommitment ranges. */ TEST_P(KFDSVMRangeTest, VramOvercommitGiantRangeTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } HSAuint64 vramSize = GetVramSize(defaultGPUNode); if (!vramSize) { LOG() << "Skipping test: No VRAM found." << std::endl; return; } unsigned long overCommitSize = 1UL << 30; /* With XNACK off, KFD checks that all SVM memory will fit into system memory */ if (vramSize + overCommitSize > GetSysMemSize() / 2) { LOG() << "Skipping test: no enough system memory." << std::endl; return; } unsigned long BufSize = vramSize + overCommitSize; HSAKMT_STATUS ret; void *pBuf; pBuf = mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, pBuf); ret = RegisterSVMRange(defaultGPUNode, pBuf, BufSize, defaultGPUNode, 0); EXPECT_EQ (HSAKMT_STATUS_SUCCESS, ret); munmap(pBuf, BufSize); TEST_END } /* * Test partial range prefault * * mmap alloc 4 pages range, memset middle 2 pages, prefetch entire range to VRAM, * use sdma to memset the rest 2 pages, each page has different value 0x1, 0x2, 0x3, 0x4 * then check if all page have the specific value after migrating 4 pages to system memory. */ TEST_P(KFDSVMRangeTest, PrefaultPartialRangeTest) { TEST_REQUIRE_ENV_CAPABILITIES(ENVCAPS_64BITLINUX); TEST_START(TESTPROFILE_RUNALL); if (!SVMAPISupported()) return; int defaultGPUNode = m_NodeInfo.HsaDefaultGPUNode(); ASSERT_GE(defaultGPUNode, 0) << "failed to get default GPU Node"; if (m_FamilyId < FAMILY_AI) { LOG() << std::hex << "Skipping test: No svm range support for family ID 0x" << m_FamilyId << "." << std::endl; return; } unsigned long BufSize = 4 * PAGE_SIZE; HSAKMT_STATUS ret; char *pBuf; pBuf = (char *)mmap(0, BufSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, pBuf); memset(pBuf + PAGE_SIZE, 0x2, PAGE_SIZE); memset(pBuf + 2 * PAGE_SIZE, 0x3, PAGE_SIZE); EXPECT_SUCCESS(RegisterSVMRange(defaultGPUNode, pBuf, BufSize, 0, 0)); EXPECT_SUCCESS(SVMRangePrefetchToNode(pBuf, BufSize, defaultGPUNode)); SDMAQueue sdmaQueue; EXPECT_SUCCESS(sdmaQueue.Create(defaultGPUNode)); sdmaQueue.PlaceAndSubmitPacket(SDMAFillDataPacket(sdmaQueue.GetFamilyId(), pBuf, 0x01010101, PAGE_SIZE)); sdmaQueue.PlaceAndSubmitPacket(SDMAFillDataPacket(sdmaQueue.GetFamilyId(), pBuf + 3 * PAGE_SIZE, 0x04040404, PAGE_SIZE)); sdmaQueue.Wait4PacketConsumption(); EXPECT_SUCCESS(sdmaQueue.Destroy()); for (int i = 0; i < 4; i++) EXPECT_EQ(pBuf[i * PAGE_SIZE], i + 1); munmap(pBuf, BufSize); TEST_END } INSTANTIATE_TEST_CASE_P(, KFDSVMRangeTest,::testing::Values(0, 1));