Add L0 sample for IPC memory

In this example, two processes are launched on the same device,
and an IPC memory handle is exchanged from server to client.

Then, the client process running copies data from its buffer
to the buffer exported by the server.

Signed-off-by: Jaime Arteaga <jaime.a.arteaga.molina@intel.com>
This commit is contained in:
Jaime Arteaga
2020-11-28 01:06:53 +00:00
committed by Compute-Runtime-Automation
parent 660ad6a1f3
commit 5f77fb9196
2 changed files with 308 additions and 0 deletions

View File

@@ -10,9 +10,16 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
zello_timestamp
zello_world_gpu
zello_world_jitc_ocloc
zello_ipc_copy_dma_buf
)
foreach(TEST_NAME ${TEST_TARGETS})
if(MSVC)
if(${TEST_NAME} STREQUAL "zello_ipc_copy_dma_buf")
continue()
endif()
endif()
add_executable(${TEST_NAME} ${TEST_NAME}.cpp)
set_target_properties(${TEST_NAME}

View File

@@ -0,0 +1,301 @@
/*
* Copyright (C) 2020 Intel Corporation
*
* SPDX-License-Identifier: MIT
*
*/
#include <level_zero/ze_api.h>
#include <fstream>
#include <iostream>
#include <limits>
#include <memory>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
template <bool TerminateOnFailure, typename ResulT>
inline void validate(ResulT result, const char *message) {
if (result == ZE_RESULT_SUCCESS) {
return;
}
if (TerminateOnFailure) {
std::cerr << (TerminateOnFailure ? "ERROR : " : "WARNING : ") << message << " : " << result
<< std::endl;
std::terminate();
}
}
#define SUCCESS_OR_TERMINATE(CALL) validate<true>(CALL, #CALL)
#define SUCCESS_OR_TERMINATE_BOOL(FLAG) validate<true>(!(FLAG), #FLAG)
#define SUCCESS_OR_WARNING(CALL) validate<false>(CALL, #CALL)
#define SUCCESS_OR_WARNING_BOOL(FLAG) validate<false>(!(FLAG), #FLAG)
size_t allocSize = 4096 + 7; // +7 to break alignment and make it harder
static int sendmsg_fd(int socket, int fd) {
char sendBuf[sizeof(ze_ipc_mem_handle_t)] = {};
char cmsgBuf[CMSG_SPACE(sizeof(ze_ipc_mem_handle_t))];
struct iovec msgBuffer;
msgBuffer.iov_base = sendBuf;
msgBuffer.iov_len = sizeof(*sendBuf);
struct msghdr msgHeader = {};
msgHeader.msg_iov = &msgBuffer;
msgHeader.msg_iovlen = 1;
msgHeader.msg_control = cmsgBuf;
msgHeader.msg_controllen = CMSG_LEN(sizeof(fd));
struct cmsghdr *controlHeader = CMSG_FIRSTHDR(&msgHeader);
controlHeader->cmsg_type = SCM_RIGHTS;
controlHeader->cmsg_level = SOL_SOCKET;
controlHeader->cmsg_len = CMSG_LEN(sizeof(fd));
*(int *)CMSG_DATA(controlHeader) = fd;
ssize_t bytesSent = sendmsg(socket, &msgHeader, 0);
if (bytesSent < 0) {
return -1;
}
return 0;
}
static int recvmsg_fd(int socket) {
int fd = -1;
char recvBuf[sizeof(ze_ipc_mem_handle_t)] = {};
char cmsgBuf[CMSG_SPACE(sizeof(ze_ipc_mem_handle_t))];
struct iovec msgBuffer;
msgBuffer.iov_base = recvBuf;
msgBuffer.iov_len = sizeof(recvBuf);
struct msghdr msgHeader = {};
msgHeader.msg_iov = &msgBuffer;
msgHeader.msg_iovlen = 1;
msgHeader.msg_control = cmsgBuf;
msgHeader.msg_controllen = CMSG_LEN(sizeof(fd));
ssize_t bytesSent = recvmsg(socket, &msgHeader, 0);
if (bytesSent < 0) {
return -1;
}
struct cmsghdr *controlHeader = CMSG_FIRSTHDR(&msgHeader);
memmove(&fd, CMSG_DATA(controlHeader), sizeof(int));
return fd;
}
inline void initializeProcess(ze_context_handle_t &context,
ze_device_handle_t &device,
ze_command_queue_handle_t &cmdQueue,
ze_command_list_handle_t &cmdList) {
SUCCESS_OR_TERMINATE(zeInit(ZE_INIT_FLAG_GPU_ONLY));
// Retrieve driver
uint32_t driverCount = 0;
SUCCESS_OR_TERMINATE(zeDriverGet(&driverCount, nullptr));
ze_driver_handle_t driverHandle;
SUCCESS_OR_TERMINATE(zeDriverGet(&driverCount, &driverHandle));
ze_context_desc_t contextDesc = {};
SUCCESS_OR_TERMINATE(zeContextCreate(driverHandle, &contextDesc, &context));
// Retrieve device
uint32_t deviceCount = 0;
SUCCESS_OR_TERMINATE(zeDeviceGet(driverHandle, &deviceCount, nullptr));
deviceCount = 1;
SUCCESS_OR_TERMINATE(zeDeviceGet(driverHandle, &deviceCount, &device));
// Print some properties
ze_device_properties_t deviceProperties = {};
SUCCESS_OR_TERMINATE(zeDeviceGetProperties(device, &deviceProperties));
std::cout << "Device : \n"
<< " * name : " << deviceProperties.name << "\n"
<< " * vendorId : " << std::hex << deviceProperties.vendorId << "\n";
// Create command queue
uint32_t numQueueGroups = 0;
SUCCESS_OR_TERMINATE(zeDeviceGetCommandQueueGroupProperties(device, &numQueueGroups, nullptr));
if (numQueueGroups == 0) {
std::cerr << "No queue groups found!\n";
std::terminate();
}
std::vector<ze_command_queue_group_properties_t> queueProperties(numQueueGroups);
SUCCESS_OR_TERMINATE(zeDeviceGetCommandQueueGroupProperties(device, &numQueueGroups,
queueProperties.data()));
ze_command_queue_desc_t cmdQueueDesc = {};
for (uint32_t i = 0; i < numQueueGroups; i++) {
if (queueProperties[i].flags & ZE_COMMAND_QUEUE_GROUP_PROPERTY_FLAG_COMPUTE) {
cmdQueueDesc.ordinal = i;
}
}
cmdQueueDesc.index = 0;
cmdQueueDesc.mode = ZE_COMMAND_QUEUE_MODE_ASYNCHRONOUS;
SUCCESS_OR_TERMINATE(zeCommandQueueCreate(context, device, &cmdQueueDesc, &cmdQueue));
// Create command list
ze_command_list_desc_t cmdListDesc = {};
cmdListDesc.commandQueueGroupOrdinal = cmdQueueDesc.ordinal;
SUCCESS_OR_TERMINATE(zeCommandListCreate(context, device, &cmdListDesc, &cmdList));
}
void run_client(int commSocket) {
std::cout << "Client process " << std::dec << getpid() << "\n";
ze_context_handle_t context;
ze_device_handle_t device;
ze_command_queue_handle_t cmdQueue;
ze_command_list_handle_t cmdList;
initializeProcess(context, device, cmdQueue, cmdList);
char *heapBuffer = new char[allocSize];
for (size_t i = 0; i < allocSize; ++i) {
heapBuffer[i] = static_cast<char>(i + 1);
}
// get the dma_buf from the other process
int dma_buf_fd = recvmsg_fd(commSocket);
if (dma_buf_fd < 0) {
std::cerr << "Failing to get dma_buf fd from server\n";
std::terminate();
}
ze_ipc_mem_handle_t pIpcHandle;
memcpy(&pIpcHandle, static_cast<void *>(&dma_buf_fd), sizeof(dma_buf_fd));
// get a memory pointer to the BO associated with the dma_buf
void *zeIpcBuffer;
SUCCESS_OR_TERMINATE(zeMemOpenIpcHandle(context, device, pIpcHandle,
0u, &zeIpcBuffer));
// Copy from heap to IPC buffer memory
SUCCESS_OR_TERMINATE(zeCommandListAppendMemoryCopy(cmdList, zeIpcBuffer, heapBuffer, allocSize,
nullptr, 0, nullptr));
SUCCESS_OR_TERMINATE(zeCommandListClose(cmdList));
SUCCESS_OR_TERMINATE(zeCommandQueueExecuteCommandLists(cmdQueue, 1, &cmdList, nullptr));
SUCCESS_OR_TERMINATE(zeCommandQueueSynchronize(cmdQueue, std::numeric_limits<uint64_t>::max()));
SUCCESS_OR_TERMINATE(zeMemCloseIpcHandle(context, zeIpcBuffer));
SUCCESS_OR_TERMINATE(zeCommandListDestroy(cmdList));
SUCCESS_OR_TERMINATE(zeCommandQueueDestroy(cmdQueue));
SUCCESS_OR_TERMINATE(zeContextDestroy(context));
delete[] heapBuffer;
}
void run_server(int commSocket, bool &validRet) {
std::cout << "Server process " << std::dec << getpid() << "\n";
ze_context_handle_t context;
ze_device_handle_t device;
ze_command_queue_handle_t cmdQueue;
ze_command_list_handle_t cmdList;
initializeProcess(context, device, cmdQueue, cmdList);
void *zeBuffer = nullptr;
ze_device_mem_alloc_desc_t deviceDesc = {};
SUCCESS_OR_TERMINATE(zeMemAllocDevice(context, &deviceDesc, allocSize, allocSize, device, &zeBuffer));
// Initialize the IPC buffer
int value = 3;
SUCCESS_OR_TERMINATE(zeCommandListAppendMemoryFill(cmdList, zeBuffer, reinterpret_cast<void *>(&value),
sizeof(value), allocSize, nullptr, 0, nullptr));
SUCCESS_OR_TERMINATE(zeCommandListClose(cmdList));
SUCCESS_OR_TERMINATE(zeCommandQueueExecuteCommandLists(cmdQueue, 1, &cmdList, nullptr));
SUCCESS_OR_TERMINATE(zeCommandQueueSynchronize(cmdQueue, std::numeric_limits<uint64_t>::max()));
SUCCESS_OR_TERMINATE(zeCommandListReset(cmdList));
// Get a dma_buf for the previously allocated pointer
ze_ipc_mem_handle_t pIpcHandle;
SUCCESS_OR_TERMINATE(zeMemGetIpcHandle(context, zeBuffer, &pIpcHandle));
// Pass the dma_buf to the other process
int dma_buf_fd;
memcpy(static_cast<void *>(&dma_buf_fd), &pIpcHandle, sizeof(dma_buf_fd));
if (sendmsg_fd(commSocket, static_cast<int>(dma_buf_fd)) < 0) {
std::cerr << "Failing to send dma_buf fd to client\n";
std::terminate();
}
char *heapBuffer = new char[allocSize];
for (size_t i = 0; i < allocSize; ++i) {
heapBuffer[i] = static_cast<char>(i + 1);
}
// Wait for child to exit
int child_status;
pid_t clientPId = wait(&child_status);
if (clientPId <= 0) {
std::cerr << "Client terminated abruptly with error code " << strerror(errno) << "\n";
std::terminate();
}
void *validateBuffer = nullptr;
ze_host_mem_alloc_desc_t hostDesc = {};
SUCCESS_OR_TERMINATE(zeMemAllocShared(context, &deviceDesc, &hostDesc,
allocSize, 1, device, &validateBuffer));
value = 5;
SUCCESS_OR_TERMINATE(zeCommandListAppendMemoryFill(cmdList, validateBuffer, reinterpret_cast<void *>(&value),
sizeof(value), allocSize, nullptr, 0, nullptr));
SUCCESS_OR_TERMINATE(zeCommandListAppendBarrier(cmdList, nullptr, 0, nullptr));
// Copy from device-allocated memory
SUCCESS_OR_TERMINATE(zeCommandListAppendMemoryCopy(cmdList, validateBuffer, zeBuffer, allocSize,
nullptr, 0, nullptr));
SUCCESS_OR_TERMINATE(zeCommandListClose(cmdList));
SUCCESS_OR_TERMINATE(zeCommandQueueExecuteCommandLists(cmdQueue, 1, &cmdList, nullptr));
SUCCESS_OR_TERMINATE(zeCommandQueueSynchronize(cmdQueue, std::numeric_limits<uint64_t>::max()));
// Validate stack and buffers have the original data from heapBuffer
validRet = (0 == memcmp(heapBuffer, validateBuffer, allocSize));
delete[] heapBuffer;
SUCCESS_OR_TERMINATE(zeMemFree(context, zeBuffer));
SUCCESS_OR_TERMINATE(zeCommandListDestroy(cmdList));
SUCCESS_OR_TERMINATE(zeCommandQueueDestroy(cmdQueue));
SUCCESS_OR_TERMINATE(zeContextDestroy(context));
}
int main(int argc, char *argv[]) {
bool outputValidationSuccessful;
int sv[2];
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
perror("socketpair");
exit(1);
}
int child = fork();
if (child < 0) {
perror("fork");
exit(1);
} else if (0 == child) {
close(sv[0]);
run_client(sv[1]);
close(sv[1]);
exit(0);
} else {
close(sv[1]);
run_server(sv[0], outputValidationSuccessful);
close(sv[0]);
}
std::cout << "\nZello IPC Results validation "
<< (outputValidationSuccessful ? "PASSED" : "FAILED")
<< std::endl;
return 0;
}