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;
+}
