Files
OpenCL-CTS/test_common/harness/alloc.cpp
Ahmed Hesham 9ba6f062d4 Add support for allocating DMA buffers (#2170)
This adds support for allocating DMA buffers on systems that support it,
i.e. Linux and Android.

On mainline Linux, starting version 5.6 (equivalent to Android 12),
there is a new kernel module framework available called [DMA-BUF
Heaps](https://github.com/torvalds/linux/blob/master/drivers/dma-buf/dma-heap.c).
The goal of this framework is to provide a standardised way for user
applications to allocate and share memory buffers between different
devices, subsystems, etc. The main feature of interest is that the
framework provides device-agnostic allocation; it abstracts away the
underlying hardware, and provides a single IOCTL,
`DMA_HEAP_IOCTL_ALLOC`. Mainline implementation provides two heaps that
act as character devices that can allocate DMA buffers; system, which
uses the buddy allocator, and cma, which uses the
[CMA](https://developer.toradex.com/software/linux-resources/linux-features/contiguous-memory-allocator-cma-linux/)
(Contiguous Memory Allocator). Both of these are [kernel configuration
options](https://github.com/torvalds/linux/blob/master/drivers/dma-buf/heaps/Kconfig)
that need to be enabled when building the Linux kernel. Generally, any
kernel module implementing this framework is made available under
/dev/dma_heaps/<heap_name>, e.g. /dev/dma_heaps/system.

The implementation currently only supports one type of DMA heaps;
`system`, the default device path for which is `/dev/dma_heap/system`.
The path can be overridden at runtime using an environment variable,
`OCL_CTS_DMA_HEAP_PATH_SYSTEM`, if needed. Extending this in the future
should be trivial (subject to platform support), by adding an entry to
the enum `dma_buf_heap_type`, and an appropriate default path and
overriding environment variable name.

The proposed implementation will conditionally compile if the conditions
are met (i.e. building for Linux or Android, using kernel headers >=
5.6.0), and will provide a compile-time warning otherwise, and return
`-1` as the DMA handle in runtime if not.

To demonstrate the functionality, a new test is added for the
`cl_khr_external_memory_dma_buf` extension. If the extension is
supported by the device, a DMA buffer will be allocated and used to
create a CL buffer, that is then used by a simple kernel.

This should provide a way forward for adding more tests that depend on
DMA buffers.

---------

Signed-off-by: Gorazd Sumkovski <gorazd.sumkovski@arm.com>
Signed-off-by: Ahmed Hesham <ahmed.hesham@arm.com>
Co-authored-by: Gorazd Sumkovski <gorazd.sumkovski@arm.com>
2025-02-26 09:51:22 -08:00

124 lines
3.6 KiB
C++

//
// Copyright (c) 2024 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "alloc.h"
#include "errorHelpers.h"
#include "testHarness.h"
#if defined(linux) || defined(__linux__) || defined(__ANDROID__)
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#include <linux/dma-heap.h>
#endif
struct dma_buf_heap_helper_t
{
dma_buf_heap_type heap_type;
const char* env_var = nullptr;
const char* default_path = nullptr;
constexpr dma_buf_heap_helper_t(dma_buf_heap_type heap_type,
const char* env_var,
const char* default_path)
: heap_type(heap_type), env_var(env_var), default_path(default_path)
{}
};
constexpr dma_buf_heap_helper_t DMA_BUF_HEAP_TABLE[] = {
{ dma_buf_heap_type::SYSTEM, "OCL_CTS_DMA_HEAP_PATH_SYSTEM",
"/dev/dma_heap/system" },
};
static dma_buf_heap_helper_t lookup_dma_heap(dma_buf_heap_type heap_type)
{
for (const auto& entry : DMA_BUF_HEAP_TABLE)
{
if (heap_type == entry.heap_type)
{
return entry;
}
}
assert(false
&& "DMA heap type does not have an entry in DMA_BUF_HEAP_TABLE");
return DMA_BUF_HEAP_TABLE[0];
}
int allocate_dma_buf(uint64_t size, dma_buf_heap_type heap_type)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
constexpr int DMA_HEAP_FLAGS = O_RDWR | O_CLOEXEC;
const auto entry = lookup_dma_heap(heap_type);
const auto override_path = getenv(entry.env_var);
const auto dma_heap_path =
(override_path == nullptr) ? entry.default_path : override_path;
const int dma_heap_fd = open(dma_heap_path, DMA_HEAP_FLAGS);
if (dma_heap_fd == -1)
{
log_error(
"Opening the DMA heap device: %s failed with error: %d (%s)\n",
dma_heap_path, errno, strerror(errno));
return TEST_SKIPPED_ITSELF;
}
dma_heap_allocation_data dma_heap_data = { 0 };
dma_heap_data.len = size;
dma_heap_data.fd_flags = O_RDWR | O_CLOEXEC;
int result = ioctl(dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, &dma_heap_data);
if (result != 0)
{
log_error("DMA heap allocation IOCTL call failed, error: %d\n", result);
close(dma_heap_fd);
return -1;
}
result = close(dma_heap_fd);
if (result == -1)
{
log_info("Failed to close the DMA heap device: %s\n", dma_heap_path);
}
return dma_heap_data.fd;
#else
#warning \
"Kernel version doesn't support DMA buffer heaps (at least v5.6.0 is required)."
return TEST_SKIPPED_ITSELF;
#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
}
#else
int allocate_dma_buf(uint64_t size, dma_buf_heap_type heap_type)
{
log_error(
"OS doesn't have DMA buffer heaps (only Linux and Android do).\n");
return TEST_SKIPPED_ITSELF;
}
#endif // defined(linux) || defined(__linux__) || defined(__ANDROID__)