// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Various unit tests for the "ntsync" synchronization primitive driver.
 *
 * Copyright (C) 2021-2022 Elizabeth Figura <zfigura@codeweavers.com>
 */

#define _GNU_SOURCE
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <linux/ntsync.h>
#include "kselftest_harness.h"

static int read_sem_state(int sem, __u32 *count, __u32 *max)
{
	struct ntsync_sem_args args;
	int ret;

	memset(&args, 0xcc, sizeof(args));
	ret = ioctl(sem, NTSYNC_IOC_SEM_READ, &args);
	*count = args.count;
	*max = args.max;
	return ret;
}

#define check_sem_state(sem, count, max) \
	({ \
		__u32 __count, __max; \
		int ret = read_sem_state((sem), &__count, &__max); \
		EXPECT_EQ(0, ret); \
		EXPECT_EQ((count), __count); \
		EXPECT_EQ((max), __max); \
	})

static int release_sem(int sem, __u32 *count)
{
	return ioctl(sem, NTSYNC_IOC_SEM_RELEASE, count);
}

static int read_mutex_state(int mutex, __u32 *count, __u32 *owner)
{
	struct ntsync_mutex_args args;
	int ret;

	memset(&args, 0xcc, sizeof(args));
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &args);
	*count = args.count;
	*owner = args.owner;
	return ret;
}

#define check_mutex_state(mutex, count, owner) \
	({ \
		__u32 __count, __owner; \
		int ret = read_mutex_state((mutex), &__count, &__owner); \
		EXPECT_EQ(0, ret); \
		EXPECT_EQ((count), __count); \
		EXPECT_EQ((owner), __owner); \
	})

static int unlock_mutex(int mutex, __u32 owner, __u32 *count)
{
	struct ntsync_mutex_args args;
	int ret;

	args.owner = owner;
	args.count = 0xdeadbeef;
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_UNLOCK, &args);
	*count = args.count;
	return ret;
}

static int read_event_state(int event, __u32 *signaled, __u32 *manual)
{
	struct ntsync_event_args args;
	int ret;

	memset(&args, 0xcc, sizeof(args));
	ret = ioctl(event, NTSYNC_IOC_EVENT_READ, &args);
	*signaled = args.signaled;
	*manual = args.manual;
	return ret;
}

#define check_event_state(event, signaled, manual) \
	({ \
		__u32 __signaled, __manual; \
		int ret = read_event_state((event), &__signaled, &__manual); \
		EXPECT_EQ(0, ret); \
		EXPECT_EQ((signaled), __signaled); \
		EXPECT_EQ((manual), __manual); \
	})

static int wait_objs(int fd, unsigned long request, __u32 count,
		     const int *objs, __u32 owner, int alert, __u32 *index)
{
	struct ntsync_wait_args args = {0};
	struct timespec timeout;
	int ret;

	clock_gettime(CLOCK_MONOTONIC, &timeout);

	args.timeout = timeout.tv_sec * 1000000000 + timeout.tv_nsec;
	args.count = count;
	args.objs = (uintptr_t)objs;
	args.owner = owner;
	args.index = 0xdeadbeef;
	args.alert = alert;
	ret = ioctl(fd, request, &args);
	*index = args.index;
	return ret;
}

static int wait_any(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index)
{
	return wait_objs(fd, NTSYNC_IOC_WAIT_ANY, count, objs, owner, 0, index);
}

static int wait_all(int fd, __u32 count, const int *objs, __u32 owner, __u32 *index)
{
	return wait_objs(fd, NTSYNC_IOC_WAIT_ALL, count, objs, owner, 0, index);
}

static int wait_any_alert(int fd, __u32 count, const int *objs,
			  __u32 owner, int alert, __u32 *index)
{
	return wait_objs(fd, NTSYNC_IOC_WAIT_ANY,
			 count, objs, owner, alert, index);
}

static int wait_all_alert(int fd, __u32 count, const int *objs,
			  __u32 owner, int alert, __u32 *index)
{
	return wait_objs(fd, NTSYNC_IOC_WAIT_ALL,
			 count, objs, owner, alert, index);
}

TEST(semaphore_state)
{
	struct ntsync_sem_args sem_args;
	struct timespec timeout;
	__u32 count, index;
	int fd, ret, sem;

	clock_gettime(CLOCK_MONOTONIC, &timeout);

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 3;
	sem_args.max = 2;
	sem = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_EQ(-1, sem);
	EXPECT_EQ(EINVAL, errno);

	sem_args.count = 2;
	sem_args.max = 2;
	sem = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, sem);
	check_sem_state(sem, 2, 2);

	count = 0;
	ret = release_sem(sem, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, count);
	check_sem_state(sem, 2, 2);

	count = 1;
	ret = release_sem(sem, &count);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOVERFLOW, errno);
	check_sem_state(sem, 2, 2);

	ret = wait_any(fd, 1, &sem, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(sem, 1, 2);

	ret = wait_any(fd, 1, &sem, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(sem, 0, 2);

	ret = wait_any(fd, 1, &sem, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	count = 3;
	ret = release_sem(sem, &count);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOVERFLOW, errno);
	check_sem_state(sem, 0, 2);

	count = 2;
	ret = release_sem(sem, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);
	check_sem_state(sem, 2, 2);

	ret = wait_any(fd, 1, &sem, 123, &index);
	EXPECT_EQ(0, ret);
	ret = wait_any(fd, 1, &sem, 123, &index);
	EXPECT_EQ(0, ret);

	count = 1;
	ret = release_sem(sem, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);
	check_sem_state(sem, 1, 2);

	count = ~0u;
	ret = release_sem(sem, &count);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOVERFLOW, errno);
	check_sem_state(sem, 1, 2);

	close(sem);

	close(fd);
}

TEST(mutex_state)
{
	struct ntsync_mutex_args mutex_args;
	__u32 owner, count, index;
	struct timespec timeout;
	int fd, ret, mutex;

	clock_gettime(CLOCK_MONOTONIC, &timeout);

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	mutex_args.owner = 123;
	mutex_args.count = 0;
	mutex = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_EQ(-1, mutex);
	EXPECT_EQ(EINVAL, errno);

	mutex_args.owner = 0;
	mutex_args.count = 2;
	mutex = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_EQ(-1, mutex);
	EXPECT_EQ(EINVAL, errno);

	mutex_args.owner = 123;
	mutex_args.count = 2;
	mutex = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, mutex);
	check_mutex_state(mutex, 2, 123);

	ret = unlock_mutex(mutex, 0, &count);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EINVAL, errno);

	ret = unlock_mutex(mutex, 456, &count);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EPERM, errno);
	check_mutex_state(mutex, 2, 123);

	ret = unlock_mutex(mutex, 123, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, count);
	check_mutex_state(mutex, 1, 123);

	ret = unlock_mutex(mutex, 123, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, count);
	check_mutex_state(mutex, 0, 0);

	ret = unlock_mutex(mutex, 123, &count);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EPERM, errno);

	ret = wait_any(fd, 1, &mutex, 456, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_mutex_state(mutex, 1, 456);

	ret = wait_any(fd, 1, &mutex, 456, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_mutex_state(mutex, 2, 456);

	ret = unlock_mutex(mutex, 456, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, count);
	check_mutex_state(mutex, 1, 456);

	ret = wait_any(fd, 1, &mutex, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	owner = 0;
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EINVAL, errno);

	owner = 123;
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EPERM, errno);
	check_mutex_state(mutex, 1, 456);

	owner = 456;
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner);
	EXPECT_EQ(0, ret);

	memset(&mutex_args, 0xcc, sizeof(mutex_args));
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	EXPECT_EQ(0, mutex_args.count);
	EXPECT_EQ(0, mutex_args.owner);

	memset(&mutex_args, 0xcc, sizeof(mutex_args));
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	EXPECT_EQ(0, mutex_args.count);
	EXPECT_EQ(0, mutex_args.owner);

	ret = wait_any(fd, 1, &mutex, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	EXPECT_EQ(0, index);
	check_mutex_state(mutex, 1, 123);

	owner = 123;
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_KILL, &owner);
	EXPECT_EQ(0, ret);

	memset(&mutex_args, 0xcc, sizeof(mutex_args));
	ret = ioctl(mutex, NTSYNC_IOC_MUTEX_READ, &mutex_args);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	EXPECT_EQ(0, mutex_args.count);
	EXPECT_EQ(0, mutex_args.owner);

	ret = wait_any(fd, 1, &mutex, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	EXPECT_EQ(0, index);
	check_mutex_state(mutex, 1, 123);

	close(mutex);

	mutex_args.owner = 0;
	mutex_args.count = 0;
	mutex = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, mutex);
	check_mutex_state(mutex, 0, 0);

	ret = wait_any(fd, 1, &mutex, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_mutex_state(mutex, 1, 123);

	close(mutex);

	mutex_args.owner = 123;
	mutex_args.count = ~0u;
	mutex = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, mutex);
	check_mutex_state(mutex, ~0u, 123);

	ret = wait_any(fd, 1, &mutex, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	close(mutex);

	close(fd);
}

TEST(manual_event_state)
{
	struct ntsync_event_args event_args;
	__u32 index, signaled;
	int fd, event, ret;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	event_args.manual = 1;
	event_args.signaled = 0;
	event = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, event);
	check_event_state(event, 0, 1);

	signaled = 0xdeadbeef;
	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(event, 1, 1);

	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);
	check_event_state(event, 1, 1);

	ret = wait_any(fd, 1, &event, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_event_state(event, 1, 1);

	signaled = 0xdeadbeef;
	ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);
	check_event_state(event, 0, 1);

	ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(event, 0, 1);

	ret = wait_any(fd, 1, &event, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);

	ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);
	check_event_state(event, 0, 1);

	ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(event, 0, 1);

	close(event);

	close(fd);
}

TEST(auto_event_state)
{
	struct ntsync_event_args event_args;
	__u32 index, signaled;
	int fd, event, ret;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	event_args.manual = 0;
	event_args.signaled = 1;
	event = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, event);

	check_event_state(event, 1, 0);

	signaled = 0xdeadbeef;
	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);
	check_event_state(event, 1, 0);

	ret = wait_any(fd, 1, &event, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_event_state(event, 0, 0);

	signaled = 0xdeadbeef;
	ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(event, 0, 0);

	ret = wait_any(fd, 1, &event, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);

	ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);
	check_event_state(event, 0, 0);

	ret = ioctl(event, NTSYNC_IOC_EVENT_PULSE, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(event, 0, 0);

	close(event);

	close(fd);
}

TEST(test_wait_any)
{
	int objs[NTSYNC_MAX_WAIT_COUNT + 1], fd, ret;
	struct ntsync_mutex_args mutex_args = {0};
	struct ntsync_sem_args sem_args = {0};
	__u32 owner, index, count, i;
	struct timespec timeout;

	clock_gettime(CLOCK_MONOTONIC, &timeout);

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 2;
	sem_args.max = 3;
	objs[0] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[0]);

	mutex_args.owner = 0;
	mutex_args.count = 0;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, objs[1]);

	ret = wait_any(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 1, 3);
	check_mutex_state(objs[1], 0, 0);

	ret = wait_any(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 0, 3);
	check_mutex_state(objs[1], 0, 0);

	ret = wait_any(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, index);
	check_sem_state(objs[0], 0, 3);
	check_mutex_state(objs[1], 1, 123);

	count = 1;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);

	ret = wait_any(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 0, 3);
	check_mutex_state(objs[1], 1, 123);

	ret = wait_any(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, index);
	check_sem_state(objs[0], 0, 3);
	check_mutex_state(objs[1], 2, 123);

	ret = wait_any(fd, 2, objs, 456, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	owner = 123;
	ret = ioctl(objs[1], NTSYNC_IOC_MUTEX_KILL, &owner);
	EXPECT_EQ(0, ret);

	ret = wait_any(fd, 2, objs, 456, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	EXPECT_EQ(1, index);

	ret = wait_any(fd, 2, objs, 456, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, index);

	close(objs[1]);

	/* test waiting on the same object twice */

	count = 2;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);

	objs[1] = objs[0];
	ret = wait_any(fd, 2, objs, 456, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 1, 3);

	ret = wait_any(fd, 0, NULL, 456, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	for (i = 1; i < NTSYNC_MAX_WAIT_COUNT + 1; ++i)
		objs[i] = objs[0];

	ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	ret = wait_any(fd, NTSYNC_MAX_WAIT_COUNT + 1, objs, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EINVAL, errno);

	ret = wait_any(fd, -1, objs, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EINVAL, errno);

	close(objs[0]);

	close(fd);
}

TEST(test_wait_all)
{
	struct ntsync_event_args event_args = {0};
	struct ntsync_mutex_args mutex_args = {0};
	struct ntsync_sem_args sem_args = {0};
	__u32 owner, index, count;
	int objs[2], fd, ret;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 2;
	sem_args.max = 3;
	objs[0] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[0]);

	mutex_args.owner = 0;
	mutex_args.count = 0;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, objs[1]);

	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 1, 3);
	check_mutex_state(objs[1], 1, 123);

	ret = wait_all(fd, 2, objs, 456, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);
	check_sem_state(objs[0], 1, 3);
	check_mutex_state(objs[1], 1, 123);

	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 0, 3);
	check_mutex_state(objs[1], 2, 123);

	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);
	check_sem_state(objs[0], 0, 3);
	check_mutex_state(objs[1], 2, 123);

	count = 3;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);

	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 2, 3);
	check_mutex_state(objs[1], 3, 123);

	owner = 123;
	ret = ioctl(objs[1], NTSYNC_IOC_MUTEX_KILL, &owner);
	EXPECT_EQ(0, ret);

	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EOWNERDEAD, errno);
	check_sem_state(objs[0], 1, 3);
	check_mutex_state(objs[1], 1, 123);

	close(objs[1]);

	event_args.manual = true;
	event_args.signaled = true;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, objs[1]);

	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);
	check_sem_state(objs[0], 0, 3);
	check_event_state(objs[1], 1, 1);

	close(objs[1]);

	/* test waiting on the same object twice */
	objs[1] = objs[0];
	ret = wait_all(fd, 2, objs, 123, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(EINVAL, errno);

	close(objs[0]);

	close(fd);
}

struct wake_args {
	int fd;
	int obj;
};

struct wait_args {
	int fd;
	unsigned long request;
	struct ntsync_wait_args *args;
	int ret;
	int err;
};

static void *wait_thread(void *arg)
{
	struct wait_args *args = arg;

	args->ret = ioctl(args->fd, args->request, args->args);
	args->err = errno;
	return NULL;
}

static __u64 get_abs_timeout(unsigned int ms)
{
	struct timespec timeout;
	clock_gettime(CLOCK_MONOTONIC, &timeout);
	return (timeout.tv_sec * 1000000000) + timeout.tv_nsec + (ms * 1000000);
}

static int wait_for_thread(pthread_t thread, unsigned int ms)
{
	struct timespec timeout;

	clock_gettime(CLOCK_REALTIME, &timeout);
	timeout.tv_nsec += ms * 1000000;
	timeout.tv_sec += (timeout.tv_nsec / 1000000000);
	timeout.tv_nsec %= 1000000000;
	return pthread_timedjoin_np(thread, NULL, &timeout);
}

TEST(wake_any)
{
	struct ntsync_event_args event_args = {0};
	struct ntsync_mutex_args mutex_args = {0};
	struct ntsync_wait_args wait_args = {0};
	struct ntsync_sem_args sem_args = {0};
	struct wait_args thread_args;
	__u32 count, index, signaled;
	int objs[2], fd, ret;
	pthread_t thread;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 0;
	sem_args.max = 3;
	objs[0] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[0]);

	mutex_args.owner = 123;
	mutex_args.count = 1;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, objs[1]);

	/* test waking the semaphore */

	wait_args.timeout = get_abs_timeout(1000);
	wait_args.objs = (uintptr_t)objs;
	wait_args.count = 2;
	wait_args.owner = 456;
	wait_args.index = 0xdeadbeef;
	thread_args.fd = fd;
	thread_args.args = &wait_args;
	thread_args.request = NTSYNC_IOC_WAIT_ANY;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	count = 1;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);
	check_sem_state(objs[0], 0, 3);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(0, wait_args.index);

	/* test waking the mutex */

	/* first grab it again for owner 123 */
	ret = wait_any(fd, 1, &objs[1], 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	wait_args.timeout = get_abs_timeout(1000);
	wait_args.owner = 456;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = unlock_mutex(objs[1], 123, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, count);

	ret = pthread_tryjoin_np(thread, NULL);
	EXPECT_EQ(EBUSY, ret);

	ret = unlock_mutex(objs[1], 123, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, mutex_args.count);
	check_mutex_state(objs[1], 1, 456);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(1, wait_args.index);

	close(objs[1]);

	/* test waking events */

	event_args.manual = false;
	event_args.signaled = false;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, objs[1]);

	wait_args.timeout = get_abs_timeout(1000);
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = ioctl(objs[1], NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(objs[1], 0, 0);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(1, wait_args.index);

	wait_args.timeout = get_abs_timeout(1000);
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = ioctl(objs[1], NTSYNC_IOC_EVENT_PULSE, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(objs[1], 0, 0);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(1, wait_args.index);

	close(objs[1]);

	event_args.manual = true;
	event_args.signaled = false;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, objs[1]);

	wait_args.timeout = get_abs_timeout(1000);
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = ioctl(objs[1], NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(objs[1], 1, 1);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(1, wait_args.index);

	ret = ioctl(objs[1], NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);

	wait_args.timeout = get_abs_timeout(1000);
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = ioctl(objs[1], NTSYNC_IOC_EVENT_PULSE, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);
	check_event_state(objs[1], 0, 1);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(1, wait_args.index);

	/* delete an object while it's being waited on */

	wait_args.timeout = get_abs_timeout(200);
	wait_args.owner = 123;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	close(objs[0]);
	close(objs[1]);

	ret = wait_for_thread(thread, 200);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(-1, thread_args.ret);
	EXPECT_EQ(ETIMEDOUT, thread_args.err);

	close(fd);
}

TEST(wake_all)
{
	struct ntsync_event_args manual_event_args = {0};
	struct ntsync_event_args auto_event_args = {0};
	struct ntsync_mutex_args mutex_args = {0};
	struct ntsync_wait_args wait_args = {0};
	struct ntsync_sem_args sem_args = {0};
	struct wait_args thread_args;
	__u32 count, index, signaled;
	int objs[4], fd, ret;
	pthread_t thread;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 0;
	sem_args.max = 3;
	objs[0] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[0]);

	mutex_args.owner = 123;
	mutex_args.count = 1;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, objs[1]);

	manual_event_args.manual = true;
	manual_event_args.signaled = true;
	objs[2] = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &manual_event_args);
	EXPECT_LE(0, objs[2]);

	auto_event_args.manual = false;
	auto_event_args.signaled = true;
	objs[3] = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &auto_event_args);
	EXPECT_EQ(0, objs[3]);

	wait_args.timeout = get_abs_timeout(1000);
	wait_args.objs = (uintptr_t)objs;
	wait_args.count = 4;
	wait_args.owner = 456;
	thread_args.fd = fd;
	thread_args.args = &wait_args;
	thread_args.request = NTSYNC_IOC_WAIT_ALL;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	count = 1;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);

	ret = pthread_tryjoin_np(thread, NULL);
	EXPECT_EQ(EBUSY, ret);

	check_sem_state(objs[0], 1, 3);

	ret = wait_any(fd, 1, &objs[0], 123, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	ret = unlock_mutex(objs[1], 123, &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, count);

	ret = pthread_tryjoin_np(thread, NULL);
	EXPECT_EQ(EBUSY, ret);

	check_mutex_state(objs[1], 0, 0);

	ret = ioctl(objs[2], NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);

	count = 2;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, count);
	check_sem_state(objs[0], 2, 3);

	ret = ioctl(objs[3], NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, signaled);

	ret = ioctl(objs[2], NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);

	ret = ioctl(objs[3], NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, signaled);

	check_sem_state(objs[0], 1, 3);
	check_mutex_state(objs[1], 1, 456);
	check_event_state(objs[2], 1, 1);
	check_event_state(objs[3], 0, 0);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);

	/* delete an object while it's being waited on */

	wait_args.timeout = get_abs_timeout(200);
	wait_args.owner = 123;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	close(objs[0]);
	close(objs[1]);
	close(objs[2]);
	close(objs[3]);

	ret = wait_for_thread(thread, 200);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(-1, thread_args.ret);
	EXPECT_EQ(ETIMEDOUT, thread_args.err);

	close(fd);
}

TEST(alert_any)
{
	struct ntsync_event_args event_args = {0};
	struct ntsync_wait_args wait_args = {0};
	struct ntsync_sem_args sem_args = {0};
	__u32 index, count, signaled;
	struct wait_args thread_args;
	int objs[2], event, fd, ret;
	pthread_t thread;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 0;
	sem_args.max = 2;
	objs[0] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[0]);

	sem_args.count = 1;
	sem_args.max = 2;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[1]);

	event_args.manual = true;
	event_args.signaled = true;
	event = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, event);

	ret = wait_any_alert(fd, 0, NULL, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);

	ret = wait_any_alert(fd, 0, NULL, 123, event, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);

	ret = wait_any_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(1, index);

	ret = wait_any_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, index);

	/* test wakeup via alert */

	ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);

	wait_args.timeout = get_abs_timeout(1000);
	wait_args.objs = (uintptr_t)objs;
	wait_args.count = 2;
	wait_args.owner = 123;
	wait_args.index = 0xdeadbeef;
	wait_args.alert = event;
	thread_args.fd = fd;
	thread_args.args = &wait_args;
	thread_args.request = NTSYNC_IOC_WAIT_ANY;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(2, wait_args.index);

	close(event);

	/* test with an auto-reset event */

	event_args.manual = false;
	event_args.signaled = true;
	event = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, event);

	count = 1;
	ret = release_sem(objs[0], &count);
	EXPECT_EQ(0, ret);

	ret = wait_any_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	ret = wait_any_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, index);

	ret = wait_any_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	close(event);

	close(objs[0]);
	close(objs[1]);

	close(fd);
}

TEST(alert_all)
{
	struct ntsync_event_args event_args = {0};
	struct ntsync_wait_args wait_args = {0};
	struct ntsync_sem_args sem_args = {0};
	struct wait_args thread_args;
	__u32 index, count, signaled;
	int objs[2], event, fd, ret;
	pthread_t thread;

	fd = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, fd);

	sem_args.count = 2;
	sem_args.max = 2;
	objs[0] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[0]);

	sem_args.count = 1;
	sem_args.max = 2;
	objs[1] = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sem_args);
	EXPECT_LE(0, objs[1]);

	event_args.manual = true;
	event_args.signaled = true;
	event = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, event);

	ret = wait_all_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	ret = wait_all_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, index);

	/* test wakeup via alert */

	ret = ioctl(event, NTSYNC_IOC_EVENT_RESET, &signaled);
	EXPECT_EQ(0, ret);

	wait_args.timeout = get_abs_timeout(1000);
	wait_args.objs = (uintptr_t)objs;
	wait_args.count = 2;
	wait_args.owner = 123;
	wait_args.index = 0xdeadbeef;
	wait_args.alert = event;
	thread_args.fd = fd;
	thread_args.args = &wait_args;
	thread_args.request = NTSYNC_IOC_WAIT_ALL;
	ret = pthread_create(&thread, NULL, wait_thread, &thread_args);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(ETIMEDOUT, ret);

	ret = ioctl(event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);

	ret = wait_for_thread(thread, 100);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, thread_args.ret);
	EXPECT_EQ(2, wait_args.index);

	close(event);

	/* test with an auto-reset event */

	event_args.manual = false;
	event_args.signaled = true;
	event = ioctl(fd, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, event);

	count = 2;
	ret = release_sem(objs[1], &count);
	EXPECT_EQ(0, ret);

	ret = wait_all_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(0, index);

	ret = wait_all_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(0, ret);
	EXPECT_EQ(2, index);

	ret = wait_all_alert(fd, 2, objs, 123, event, &index);
	EXPECT_EQ(-1, ret);
	EXPECT_EQ(ETIMEDOUT, errno);

	close(event);

	close(objs[0]);
	close(objs[1]);

	close(fd);
}

#define STRESS_LOOPS 10000
#define STRESS_THREADS 4

static unsigned int stress_counter;
static int stress_device, stress_start_event, stress_mutex;

static void *stress_thread(void *arg)
{
	struct ntsync_wait_args wait_args = {0};
	__u32 index, count, i;
	int ret;

	wait_args.timeout = UINT64_MAX;
	wait_args.count = 1;
	wait_args.objs = (uintptr_t)&stress_start_event;
	wait_args.owner = gettid();
	wait_args.index = 0xdeadbeef;

	ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args);

	wait_args.objs = (uintptr_t)&stress_mutex;

	for (i = 0; i < STRESS_LOOPS; ++i) {
		ioctl(stress_device, NTSYNC_IOC_WAIT_ANY, &wait_args);

		++stress_counter;

		unlock_mutex(stress_mutex, wait_args.owner, &count);
	}

	return NULL;
}

TEST(stress_wait)
{
	struct ntsync_event_args event_args;
	struct ntsync_mutex_args mutex_args;
	pthread_t threads[STRESS_THREADS];
	__u32 signaled, i;
	int ret;

	stress_device = open("/dev/ntsync", O_CLOEXEC | O_RDONLY);
	ASSERT_LE(0, stress_device);

	mutex_args.owner = 0;
	mutex_args.count = 0;
	stress_mutex = ioctl(stress_device, NTSYNC_IOC_CREATE_MUTEX, &mutex_args);
	EXPECT_LE(0, stress_mutex);

	event_args.manual = 1;
	event_args.signaled = 0;
	stress_start_event = ioctl(stress_device, NTSYNC_IOC_CREATE_EVENT, &event_args);
	EXPECT_LE(0, stress_start_event);

	for (i = 0; i < STRESS_THREADS; ++i)
		pthread_create(&threads[i], NULL, stress_thread, NULL);

	ret = ioctl(stress_start_event, NTSYNC_IOC_EVENT_SET, &signaled);
	EXPECT_EQ(0, ret);

	for (i = 0; i < STRESS_THREADS; ++i) {
		ret = pthread_join(threads[i], NULL);
		EXPECT_EQ(0, ret);
	}

	EXPECT_EQ(STRESS_LOOPS * STRESS_THREADS, stress_counter);

	close(stress_start_event);
	close(stress_mutex);
	close(stress_device);
}

TEST_HARNESS_MAIN
