An initial pass at a drm_syncobj API test. Currently covers trivial use of: DRM_IOCTL_SYNCOBJ_CREATE DRM_IOCTL_SYNCOBJ_DESTROY DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE DRM_IOCTL_SYNCOBJ_WAIT DRM_IOCTL_SYNCOBJ_RESET DRM_IOCTL_SYNCOBJ_SIGNAL DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL And demonstrates how the userspace API can be used, along with some fairly simple bad parameter checking. The patch includes a few helpers taken from libdrm, as at least on the VM I was testing with, I didn't have a new enough libdrm to support the *_TIMELINE_* ioctls. Ideally the ioctl-helper bits can be dropped at a later time. Feedback would be appreciated! Cc: Maarten Lankhorst <maarten.lankhorst@xxxxxxxxxxxxxxx> Cc: Maxime Ripard <mripard@xxxxxxxxxx> Cc: Thomas Zimmermann <tzimmermann@xxxxxxx> Cc: Jason Ekstrand <jason@xxxxxxxxxxxxxx> Cc: Christian König <christian.koenig@xxxxxxx> Cc: Lionel Landwerlin <lionel.g.landwerlin@xxxxxxxxx> Cc: Chunming Zhou <david1.zhou@xxxxxxx> Cc: David Airlie <airlied@xxxxxxxx> Cc: Daniel Vetter <daniel@xxxxxxxx> Cc: Shuah Khan <shuah@xxxxxxxxxx> Cc: dri-devel@xxxxxxxxxxxxxxxxxxxxx Signed-off-by: John Stultz <jstultz@xxxxxxxxxx> --- .../drivers/gpu/drm_syncobj/Makefile | 11 + .../drivers/gpu/drm_syncobj/ioctl-helper.c | 85 ++++ .../drivers/gpu/drm_syncobj/ioctl-helper.h | 74 ++++ .../drivers/gpu/drm_syncobj/syncobj-test.c | 410 ++++++++++++++++++ 4 files changed, 580 insertions(+) create mode 100644 tools/testing/selftests/drivers/gpu/drm_syncobj/Makefile create mode 100644 tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.c create mode 100644 tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.h create mode 100644 tools/testing/selftests/drivers/gpu/drm_syncobj/syncobj-test.c diff --git a/tools/testing/selftests/drivers/gpu/drm_syncobj/Makefile b/tools/testing/selftests/drivers/gpu/drm_syncobj/Makefile new file mode 100644 index 000000000000..6576d9b2006c --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/drm_syncobj/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -I/usr/include/libdrm/ +LDFLAGS += -pthread -ldrm + +TEST_GEN_FILES= syncobj-test + +include ../../../lib.mk + +$(OUTPUT)/syncobj-test: syncobj-test.c ioctl-helper.c +EXTRA_CLEAN = $(OUTPUT)/ioctl-helper.o + diff --git a/tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.c b/tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.c new file mode 100644 index 000000000000..e5c59c9bed36 --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include <errno.h> +#include <libdrm/drm.h> +#include <xf86drm.h> +#include "ioctl-helper.h" + +#ifndef DRM_CAP_SYNCOBJ_TIMELINE +/* + * The following is nabbed from libdrm's xf86drm.c as the + * installed libdrm doesn't yet include these definitions + * + * + * \author Rickard E. (Rik) Faith <faith@xxxxxxxxxxx> + * \author Kevin E. Martin <martin@xxxxxxxxxxx> + */ +/* + * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. + * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. + * 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 (including the next + * paragraph) 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 + * PRECISION INSIGHT AND/OR ITS SUPPLIERS 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. + */ +int drmSyncobjTimelineSignal(int fd, const uint32_t *handles, + uint64_t *points, uint32_t handle_count) +{ + struct drm_syncobj_timeline_array args; + int ret; + + memset(&args, 0, sizeof(args)); + args.handles = (uintptr_t)handles; + args.points = (uintptr_t)points; + args.count_handles = handle_count; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &args); + return ret; +} + +int drmSyncobjTimelineWait(int fd, uint32_t *handles, uint64_t *points, + unsigned int num_handles, + int64_t timeout_nsec, unsigned int flags, + uint32_t *first_signaled) +{ + struct drm_syncobj_timeline_wait args; + int ret; + + memset(&args, 0, sizeof(args)); + args.handles = (uintptr_t)handles; + args.points = (uintptr_t)points; + args.timeout_nsec = timeout_nsec; + args.count_handles = num_handles; + args.flags = flags; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, &args); + if (ret < 0) + return -errno; + + if (first_signaled) + *first_signaled = args.first_signaled; + return ret; +} + +#endif diff --git a/tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.h b/tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.h new file mode 100644 index 000000000000..b0c1025034b5 --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/drm_syncobj/ioctl-helper.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef __IOCTL_HELPER_H__ +#define __IOCTL_HELPER_H__ + +/* Bits pulled from libdrm's include/drm/drm.h */ +#ifndef DRM_CAP_SYNCOBJ_TIMELINE +/* + * Header for the Direct Rendering Manager + * + * Author: Rickard E. (Rik) Faith <faith@xxxxxxxxxxx> + * + * Acknowledgments: + * Dec 1999, Richard Henderson <rth@xxxxxxxxxxx>, move to generic cmpxchg. + */ + +/* + * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. + * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. + * 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 (including the next + * paragraph) 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 + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS 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. + */ +struct drm_syncobj_timeline_wait { + __u64 handles; + /* wait on specific timeline point for every handles*/ + __u64 points; + /* absolute timeout */ + __s64 timeout_nsec; + __u32 count_handles; + __u32 flags; + __u32 first_signaled; /* only valid when not waiting all */ + __u32 pad; +}; + + +#define DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED (1 << 0) +struct drm_syncobj_timeline_array { + __u64 handles; + __u64 points; + __u32 count_handles; + __u32 flags; +}; + +#define DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT DRM_IOWR(0xCA, struct drm_syncobj_timeline_wait) +#define DRM_IOCTL_SYNCOBJ_QUERY DRM_IOWR(0xCB, struct drm_syncobj_timeline_array) +#define DRM_IOCTL_SYNCOBJ_TRANSFER DRM_IOWR(0xCC, struct drm_syncobj_transfer) +#define DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL DRM_IOWR(0xCD, struct drm_syncobj_timeline_array) + +int drmSyncobjTimelineSignal(int fd, const uint32_t *handles, + uint64_t *points, uint32_t handle_count); +int drmSyncobjTimelineWait(int fd, uint32_t *handles, uint64_t *points, + unsigned int num_handles, + int64_t timeout_nsec, unsigned int flags, + uint32_t *first_signaled); +#endif +#endif /*__IOCTL_HELPER_H__*/ + diff --git a/tools/testing/selftests/drivers/gpu/drm_syncobj/syncobj-test.c b/tools/testing/selftests/drivers/gpu/drm_syncobj/syncobj-test.c new file mode 100644 index 000000000000..21474b0d3b9e --- /dev/null +++ b/tools/testing/selftests/drivers/gpu/drm_syncobj/syncobj-test.c @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This test exercises basic syncobj ioctl interfaces from + * userland via the libdrm helpers. + * + * Copyright (C) 2022, Google LLC. + * + * Currently covers trivial use of: + * DRM_IOCTL_SYNCOBJ_CREATE + * DRM_IOCTL_SYNCOBJ_DESTROY + * DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD + * DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE + * DRM_IOCTL_SYNCOBJ_WAIT + * DRM_IOCTL_SYNCOBJ_RESET + * DRM_IOCTL_SYNCOBJ_SIGNAL + * DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT + * DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL + * + * TODO: Need coverage for the following ioctls: + * DRM_IOCTL_SYNCOBJ_QUERY + * DRM_IOCTL_SYNCOBJ_TRANSFER + * As well as more complicated use of interface (like + * signal/wait with multiple handles, etc), and sync_file + * import/export. + */ +#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 <time.h> +#include <poll.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <pthread.h> +#include <linux/dma-buf.h> +#include <libdrm/drm.h> +#include <xf86drm.h> + +#include "ioctl-helper.h" + + +#define NSEC_PER_SEC 1000000000ULL +static uint64_t get_abs_timeout(uint64_t rel_nsec) +{ + struct timespec ts; + uint64_t ns; + + clock_gettime(CLOCK_MONOTONIC, &ts); + ns = ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; + ns += rel_nsec; + return ns; +} + +struct test_arg { + int dev_fd; + uint32_t handle; + int handle_fd; +}; +#define TEST_TIMES 5 + +void *syncobj_signal_reset(void *arg) +{ + struct test_arg *d = (struct test_arg *)arg; + int ret; + int i; + + for (i = 0; i < TEST_TIMES; i++) { + sleep(3); + printf("%s: sending signal!\n", __func__); + ret = drmSyncobjSignal(d->dev_fd, &d->handle, 1); + if (ret) + printf("Signal failed %i\n", ret); + } + return NULL; +} + +static int syncobj_wait_reset(struct test_arg *d) +{ + uint64_t abs_timeout; + int ret; + int i; + + for (i = 0; i < TEST_TIMES; i++) { + abs_timeout = get_abs_timeout(10*NSEC_PER_SEC); + printf("%s calling drmSyncobjWait\n", __func__); + ret = drmSyncobjWait(d->dev_fd, &d->handle, 1, abs_timeout, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT, + NULL); + if (ret) { + printf("Error: syncobjwait failed %i\n", ret); + break; + } + printf("%s: drmSyncobjWait returned!\n", __func__); + + ret = drmSyncobjReset(d->dev_fd, &d->handle, 1); + if (ret) { + printf("Error: syncobjreset failed\n"); + break; + } + } + return ret; +} + +void *syncobj_signal_timeline(void *arg) +{ + struct test_arg *d = (struct test_arg *)arg; + uint64_t point = 0; + int ret; + + for (point = 0; point <= (TEST_TIMES-1)*5; point++) { + sleep(1); + printf("%s: sending signal %lld!\n", __func__, point); + ret = drmSyncobjTimelineSignal(d->dev_fd, &d->handle, &point, 1); + if (ret) + printf("Signal failed %i\n", ret); + } + return NULL; +} + + +int syncobj_timeline_wait(struct test_arg *d) +{ + uint64_t abs_timeout; + uint64_t point; + int ret; + int i; + + for (i = 0; i < TEST_TIMES; i++) { + abs_timeout = get_abs_timeout(10*NSEC_PER_SEC); + + point = i * 5; + printf("%s: drmSyncobjTimelineWait waiting on %lld!\n", __func__, point); + ret = drmSyncobjTimelineWait(d->dev_fd, &d->handle, &point, 1, + abs_timeout, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT, + NULL); + if (ret) { + printf("Error: syncobjwait failed %i\n", ret); + return ret; + } + printf("%s: drmSyncobjTimelineWait got %lld!\n", __func__, point); + } + return 0; +} + + +static int test_thread_signal_wait(int devfd, void *(*signal_fn)(void *), + int (*wait_fn)(struct test_arg *)) +{ + uint32_t handle; + struct test_arg d; + pthread_t pth; + int ret; + + ret = drmSyncobjCreate(devfd, 0, &handle); + if (ret) { + printf("Error: Couldn't create syncobj\n"); + return ret; + } + + d.dev_fd = devfd; + d.handle = handle; + + pthread_create(&pth, 0, signal_fn, &d); + ret = wait_fn(&d); + pthread_join(pth, NULL); + drmSyncobjDestroy(devfd, handle); + + return ret; +} + +static int test_fork_signal_wait(int devfd, void *(*signal_fn)(void *), + int (*wait_fn)(struct test_arg *)) +{ + uint32_t handle; + struct test_arg p, c; + pid_t id; + int ret; + + ret = drmSyncobjCreate(devfd, 0, &handle); + if (ret) { + printf("Error: Couldn't create syncobj\n"); + return ret; + } + + p.dev_fd = devfd; + p.handle = 0; + p.handle_fd = 0; + c = p; + p.handle = handle; + + ret = drmSyncobjHandleToFD(devfd, handle, &c.handle_fd); + if (ret) { + printf("Error: Couldn't convert handle to fd\n"); + goto out; + } + + id = fork(); + if (id == 0) { + ret = drmSyncobjFDToHandle(c.dev_fd, c.handle_fd, &c.handle); + if (ret) { + printf("Error: Couldn't convert fd to handle\n"); + exit(-1); + } + close(c.handle_fd); + signal_fn((void *)&c); + exit(0); + } else { + ret = wait_fn(&p); + waitpid(id, 0, 0); + } + +out: + if (c.handle_fd) + close(c.handle_fd); + drmSyncobjDestroy(devfd, handle); + + return ret; +} + + +static int test_badparameters(int devfd) +{ + uint32_t handle1, handle2; + int ret, fail = 0; + + /* create bad fd */ + ret = drmSyncobjCreate(-1, 0, &handle1); + if (!ret || errno != EBADF) { + printf("drmSyncobjCreate - bad fd fails! (%i != EBADF)\n", errno); + fail = 1; + } + /* destroy bad fd */ + ret = drmSyncobjDestroy(-1, handle1); + if (!ret || errno != EBADF) { + printf("drmSyncobjDestroy - bad fd fails! (%i != EBADF)\n", errno); + fail = 1; + } + + /* TODO: Bad flags */ + + ret = drmSyncobjCreate(devfd, 0, &handle1); + if (ret) { + printf("drmSyncobjCreate - unexpected failure!\n"); + fail = 1; + } + + /* Destroy zeroed handle */ + handle2 = 0; + ret = drmSyncobjDestroy(devfd, handle2); + if (!ret || errno != EINVAL) { + printf("drmSyncobjDestroy - zero'ed handle! (%i != EINVAL)\n", errno); + fail = 1; + } + /* Destroy invalid handle */ + handle2 = -1; + ret = drmSyncobjDestroy(devfd, handle2); + if (!ret || errno != EINVAL) { + printf("drmSyncobjDestroy - invalid handle! (%i != EINVAL)\n", errno); + fail = 1; + } + + /* invalid timeouts */ + ret = drmSyncobjWait(devfd, &handle1, 1, 1000, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT, + NULL); + if (!ret || errno != ETIME) { + printf("drmSyncobjWait - invalid timeout (relative)! (%i != ETIME)\n", errno); + fail = 1; + } + + ret = drmSyncobjWait(devfd, &handle1, 1, -1, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT, + NULL); + if (!ret || errno != ETIME) { + printf("drmSyncobjWait - invalid timeout (-1)! (%i != ETIME)\n", errno); + fail = 1; + } + + ret = drmSyncobjDestroy(devfd, handle1); + if (ret) { + printf("drmSyncobjDestroy - unexpected failure!\n"); + fail = 1; + } + + + return fail; +} + + +#define NAME_LEN 16 +static int check_device(int fd) +{ + drm_version_t version = { 0 }; + char name[NAME_LEN]; + uint32_t handle; + int ret; + + memset(name, 0, NAME_LEN); + version.name_len = NAME_LEN; + version.name = name; + + ret = ioctl(fd, DRM_IOCTL_VERSION, &version); + if (ret) + return -1; + + printf("%s name: %s\n", __func__, name); + + ret = drmSyncobjCreate(fd, 0, &handle); + if (!ret) { + drmSyncobjDestroy(fd, handle); + printf("%s selected: %s\n", __func__, name); + } + return ret; +} + +static int find_device(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_device(fd)) { + close(fd); + fd = -1; + continue; + } else { + break; + } + } + return fd; +} + +int main(int argc, char **argv) +{ + int devfd = find_device(); + char *testname; + int ret; + + if (devfd < 0) { + printf("Error: Couldn't find supported drm device\n"); + return devfd; + } + + testname = "Bad parameters test"; + printf("\n%s\n", testname); + printf("===================\n"); + ret = test_badparameters(devfd); + if (ret) + printf("%s: FAILED\n", testname); + else + printf("%s: PASSED\n", testname); + + + testname = "Threaded reset test"; + printf("\n%s\n", testname); + printf("===================\n"); + ret = test_thread_signal_wait(devfd, &syncobj_signal_reset, &syncobj_wait_reset); + if (ret) + printf("%s: FAILED\n", testname); + else + printf("%s: PASSED\n", testname); + + testname = "Threaded timeline test"; + printf("\n%s\n", testname); + printf("===================\n"); + ret = test_thread_signal_wait(devfd, &syncobj_signal_timeline, &syncobj_timeline_wait); + if (ret) + printf("%s: FAILED\n", testname); + else + printf("%s: PASSED\n", testname); + + + testname = "Forked reset test"; + printf("\n%s\n", testname); + printf("===================\n"); + ret = test_fork_signal_wait(devfd, &syncobj_signal_reset, &syncobj_wait_reset); + if (ret) + printf("\n%s: FAILED\n", testname); + else + printf("\n%s: PASSED\n", testname); + + testname = "Forked timeline test"; + printf("\n%s\n", testname); + printf("===================\n"); + ret = test_fork_signal_wait(devfd, &syncobj_signal_timeline, &syncobj_timeline_wait); + if (ret) + printf("\n%s: FAILED\n", testname); + else + printf("\n%s: PASSED\n", testname); + + + close(devfd); + return 0; +} -- 2.37.0.144.g8ac04bfd2-goog