linux-stable/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c
John Stultz 1d317c1ca2 kselftests: dmabuf-heaps: Add extra checking that allocated buffers are zeroed
Add a check to validate that buffers allocated from the heaps
are properly zeroed before being given to userland.

It is done by allocating a number of buffers, and filling them
with a nonzero pattern, then closing and reallocating more
buffers and checking that they are all properly zeroed.

This is helpful to validate any cached buffers are zeroed
before being given back out.

Cc: Shuah Khan <shuah@kernel.org>
Cc: Brian Starkey <brian.starkey@arm.com>
Cc: Sumit Semwal <sumit.semwal@linaro.org>
Cc: Laura Abbott <labbott@kernel.org>
Cc: Hridya Valsaraju <hridya@google.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Sandeep Patil <sspatil@google.com>
Cc: Daniel Mentz <danielmentz@google.com>
Cc: linux-media@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kselftest@vger.kernel.org
Signed-off-by: John Stultz <john.stultz@linaro.org>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2021-02-08 16:25:53 -07:00

486 lines
9.6 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <linux/dma-buf.h>
#include <drm/drm.h>
#include "../../../../include/uapi/linux/dma-heap.h"
#define DEVPATH "/dev/dma_heap"
static int check_vgem(int fd)
{
drm_version_t version = { 0 };
char name[5];
int ret;
version.name_len = 4;
version.name = name;
ret = ioctl(fd, DRM_IOCTL_VERSION, &version);
if (ret)
return 0;
return !strcmp(name, "vgem");
}
static int open_vgem(void)
{
int i, fd;
const char *drmstr = "/dev/dri/card";
fd = -1;
for (i = 0; i < 16; i++) {
char name[80];
snprintf(name, 80, "%s%u", drmstr, i);
fd = open(name, O_RDWR);
if (fd < 0)
continue;
if (!check_vgem(fd)) {
close(fd);
fd = -1;
continue;
} else {
break;
}
}
return fd;
}
static int import_vgem_fd(int vgem_fd, int dma_buf_fd, uint32_t *handle)
{
struct drm_prime_handle import_handle = {
.fd = dma_buf_fd,
.flags = 0,
.handle = 0,
};
int ret;
ret = ioctl(vgem_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import_handle);
if (ret == 0)
*handle = import_handle.handle;
return ret;
}
static void close_handle(int vgem_fd, uint32_t handle)
{
struct drm_gem_close close = {
.handle = handle,
};
ioctl(vgem_fd, DRM_IOCTL_GEM_CLOSE, &close);
}
static int dmabuf_heap_open(char *name)
{
int ret, fd;
char buf[256];
ret = snprintf(buf, 256, "%s/%s", DEVPATH, name);
if (ret < 0) {
printf("snprintf failed!\n");
return ret;
}
fd = open(buf, O_RDWR);
if (fd < 0)
printf("open %s failed!\n", buf);
return fd;
}
static int dmabuf_heap_alloc_fdflags(int fd, size_t len, unsigned int fd_flags,
unsigned int heap_flags, int *dmabuf_fd)
{
struct dma_heap_allocation_data data = {
.len = len,
.fd = 0,
.fd_flags = fd_flags,
.heap_flags = heap_flags,
};
int ret;
if (!dmabuf_fd)
return -EINVAL;
ret = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
if (ret < 0)
return ret;
*dmabuf_fd = (int)data.fd;
return ret;
}
static int dmabuf_heap_alloc(int fd, size_t len, unsigned int flags,
int *dmabuf_fd)
{
return dmabuf_heap_alloc_fdflags(fd, len, O_RDWR | O_CLOEXEC, flags,
dmabuf_fd);
}
static int dmabuf_sync(int fd, int start_stop)
{
struct dma_buf_sync sync = {
.flags = start_stop | DMA_BUF_SYNC_RW,
};
return ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
}
#define ONE_MEG (1024 * 1024)
static int test_alloc_and_import(char *heap_name)
{
int heap_fd = -1, dmabuf_fd = -1, importer_fd = -1;
uint32_t handle = 0;
void *p = NULL;
int ret;
heap_fd = dmabuf_heap_open(heap_name);
if (heap_fd < 0)
return -1;
printf(" Testing allocation and importing: ");
ret = dmabuf_heap_alloc(heap_fd, ONE_MEG, 0, &dmabuf_fd);
if (ret) {
printf("FAIL (Allocation Failed!)\n");
ret = -1;
goto out;
}
/* mmap and write a simple pattern */
p = mmap(NULL,
ONE_MEG,
PROT_READ | PROT_WRITE,
MAP_SHARED,
dmabuf_fd,
0);
if (p == MAP_FAILED) {
printf("FAIL (mmap() failed)\n");
ret = -1;
goto out;
}
dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_START);
memset(p, 1, ONE_MEG / 2);
memset((char *)p + ONE_MEG / 2, 0, ONE_MEG / 2);
dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_END);
importer_fd = open_vgem();
if (importer_fd < 0) {
ret = importer_fd;
printf("(Could not open vgem - skipping): ");
} else {
ret = import_vgem_fd(importer_fd, dmabuf_fd, &handle);
if (ret < 0) {
printf("FAIL (Failed to import buffer)\n");
goto out;
}
}
ret = dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_START);
if (ret < 0) {
printf("FAIL (DMA_BUF_SYNC_START failed!)\n");
goto out;
}
memset(p, 0xff, ONE_MEG);
ret = dmabuf_sync(dmabuf_fd, DMA_BUF_SYNC_END);
if (ret < 0) {
printf("FAIL (DMA_BUF_SYNC_END failed!)\n");
goto out;
}
close_handle(importer_fd, handle);
ret = 0;
printf(" OK\n");
out:
if (p)
munmap(p, ONE_MEG);
if (importer_fd >= 0)
close(importer_fd);
if (dmabuf_fd >= 0)
close(dmabuf_fd);
if (heap_fd >= 0)
close(heap_fd);
return ret;
}
static int test_alloc_zeroed(char *heap_name, size_t size)
{
int heap_fd = -1, dmabuf_fd[32];
int i, j, ret;
void *p = NULL;
char *c;
printf(" Testing alloced %ldk buffers are zeroed: ", size / 1024);
heap_fd = dmabuf_heap_open(heap_name);
if (heap_fd < 0)
return -1;
/* Allocate and fill a bunch of buffers */
for (i = 0; i < 32; i++) {
ret = dmabuf_heap_alloc(heap_fd, size, 0, &dmabuf_fd[i]);
if (ret < 0) {
printf("FAIL (Allocation (%i) failed)\n", i);
goto out;
}
/* mmap and fill with simple pattern */
p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd[i], 0);
if (p == MAP_FAILED) {
printf("FAIL (mmap() failed!)\n");
ret = -1;
goto out;
}
dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_START);
memset(p, 0xff, size);
dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_END);
munmap(p, size);
}
/* close them all */
for (i = 0; i < 32; i++)
close(dmabuf_fd[i]);
/* Allocate and validate all buffers are zeroed */
for (i = 0; i < 32; i++) {
ret = dmabuf_heap_alloc(heap_fd, size, 0, &dmabuf_fd[i]);
if (ret < 0) {
printf("FAIL (Allocation (%i) failed)\n", i);
goto out;
}
/* mmap and validate everything is zero */
p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd[i], 0);
if (p == MAP_FAILED) {
printf("FAIL (mmap() failed!)\n");
ret = -1;
goto out;
}
dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_START);
c = (char *)p;
for (j = 0; j < size; j++) {
if (c[j] != 0) {
printf("FAIL (Allocated buffer not zeroed @ %i)\n", j);
break;
}
}
dmabuf_sync(dmabuf_fd[i], DMA_BUF_SYNC_END);
munmap(p, size);
}
/* close them all */
for (i = 0; i < 32; i++)
close(dmabuf_fd[i]);
close(heap_fd);
printf("OK\n");
return 0;
out:
while (i > 0) {
close(dmabuf_fd[i]);
i--;
}
close(heap_fd);
return ret;
}
/* Test the ioctl version compatibility w/ a smaller structure then expected */
static int dmabuf_heap_alloc_older(int fd, size_t len, unsigned int flags,
int *dmabuf_fd)
{
int ret;
unsigned int older_alloc_ioctl;
struct dma_heap_allocation_data_smaller {
__u64 len;
__u32 fd;
__u32 fd_flags;
} data = {
.len = len,
.fd = 0,
.fd_flags = O_RDWR | O_CLOEXEC,
};
older_alloc_ioctl = _IOWR(DMA_HEAP_IOC_MAGIC, 0x0,
struct dma_heap_allocation_data_smaller);
if (!dmabuf_fd)
return -EINVAL;
ret = ioctl(fd, older_alloc_ioctl, &data);
if (ret < 0)
return ret;
*dmabuf_fd = (int)data.fd;
return ret;
}
/* Test the ioctl version compatibility w/ a larger structure then expected */
static int dmabuf_heap_alloc_newer(int fd, size_t len, unsigned int flags,
int *dmabuf_fd)
{
int ret;
unsigned int newer_alloc_ioctl;
struct dma_heap_allocation_data_bigger {
__u64 len;
__u32 fd;
__u32 fd_flags;
__u64 heap_flags;
__u64 garbage1;
__u64 garbage2;
__u64 garbage3;
} data = {
.len = len,
.fd = 0,
.fd_flags = O_RDWR | O_CLOEXEC,
.heap_flags = flags,
.garbage1 = 0xffffffff,
.garbage2 = 0x88888888,
.garbage3 = 0x11111111,
};
newer_alloc_ioctl = _IOWR(DMA_HEAP_IOC_MAGIC, 0x0,
struct dma_heap_allocation_data_bigger);
if (!dmabuf_fd)
return -EINVAL;
ret = ioctl(fd, newer_alloc_ioctl, &data);
if (ret < 0)
return ret;
*dmabuf_fd = (int)data.fd;
return ret;
}
static int test_alloc_compat(char *heap_name)
{
int heap_fd = -1, dmabuf_fd = -1;
int ret;
heap_fd = dmabuf_heap_open(heap_name);
if (heap_fd < 0)
return -1;
printf(" Testing (theoretical)older alloc compat: ");
ret = dmabuf_heap_alloc_older(heap_fd, ONE_MEG, 0, &dmabuf_fd);
if (ret) {
printf("FAIL (Older compat allocation failed!)\n");
ret = -1;
goto out;
}
close(dmabuf_fd);
printf("OK\n");
printf(" Testing (theoretical)newer alloc compat: ");
ret = dmabuf_heap_alloc_newer(heap_fd, ONE_MEG, 0, &dmabuf_fd);
if (ret) {
printf("FAIL (Newer compat allocation failed!)\n");
ret = -1;
goto out;
}
printf("OK\n");
out:
if (dmabuf_fd >= 0)
close(dmabuf_fd);
if (heap_fd >= 0)
close(heap_fd);
return ret;
}
static int test_alloc_errors(char *heap_name)
{
int heap_fd = -1, dmabuf_fd = -1;
int ret;
heap_fd = dmabuf_heap_open(heap_name);
if (heap_fd < 0)
return -1;
printf(" Testing expected error cases: ");
ret = dmabuf_heap_alloc(0, ONE_MEG, 0x111111, &dmabuf_fd);
if (!ret) {
printf("FAIL (Did not see expected error (invalid fd)!)\n");
ret = -1;
goto out;
}
ret = dmabuf_heap_alloc(heap_fd, ONE_MEG, 0x111111, &dmabuf_fd);
if (!ret) {
printf("FAIL (Did not see expected error (invalid heap flags)!)\n");
ret = -1;
goto out;
}
ret = dmabuf_heap_alloc_fdflags(heap_fd, ONE_MEG,
~(O_RDWR | O_CLOEXEC), 0, &dmabuf_fd);
if (!ret) {
printf("FAIL (Did not see expected error (invalid fd flags)!)\n");
ret = -1;
goto out;
}
printf("OK\n");
ret = 0;
out:
if (dmabuf_fd >= 0)
close(dmabuf_fd);
if (heap_fd >= 0)
close(heap_fd);
return ret;
}
int main(void)
{
DIR *d;
struct dirent *dir;
int ret = -1;
d = opendir(DEVPATH);
if (!d) {
printf("No %s directory?\n", DEVPATH);
return -1;
}
while ((dir = readdir(d)) != NULL) {
if (!strncmp(dir->d_name, ".", 2))
continue;
if (!strncmp(dir->d_name, "..", 3))
continue;
printf("Testing heap: %s\n", dir->d_name);
printf("=======================================\n");
ret = test_alloc_and_import(dir->d_name);
if (ret)
break;
ret = test_alloc_zeroed(dir->d_name, 4 * 1024);
if (ret)
break;
ret = test_alloc_zeroed(dir->d_name, ONE_MEG);
if (ret)
break;
ret = test_alloc_compat(dir->d_name);
if (ret)
break;
ret = test_alloc_errors(dir->d_name);
if (ret)
break;
}
closedir(d);
return ret;
}