root/tools/testing/selftests/drivers/ntsync/ntsync.c
// 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