Messages in this thread | | | Date | Tue, 12 Jul 2022 09:43:44 +0200 | Subject | Re: [RFC][PATCH 3/3] kselftest: Add drm_syncobj API test tool | From | Christian König <> |
| |
Am 12.07.22 um 06:22 schrieb John Stultz: > 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@linux.intel.com> > Cc: Maxime Ripard <mripard@kernel.org> > Cc: Thomas Zimmermann <tzimmermann@suse.de> > Cc: Jason Ekstrand <jason@jlekstrand.net> > Cc: Christian König <christian.koenig@amd.com> > Cc: Lionel Landwerlin <lionel.g.landwerlin@intel.com> > Cc: Chunming Zhou <david1.zhou@amd.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Daniel Vetter <daniel@ffwll.ch> > Cc: Shuah Khan <shuah@kernel.org> > Cc: dri-devel@lists.freedesktop.org > Signed-off-by: John Stultz <jstultz@google.com> > --- > .../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 ++++++++++++++++++
DRM userspace selftests usually go either into libdrm or igt and not into the kernel source.
If you want to make in kernel self tests they should go into drivers/gpu/drm/selftests/
Regards, Christian.
> 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@valinux.com> > + * \author Kevin E. Martin <martin@valinux.com> > + */ > +/* > + * 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@valinux.com> > + * > + * Acknowledgments: > + * Dec 1999, Richard Henderson <rth@twiddle.net>, 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; > +}
| |