linux/tools/testing/selftests/drivers/ntsync/ntsync.c
Elizabeth Figura a22860e57b selftests: ntsync: Add a stress test for contended waits.
Test a more realistic usage pattern, and one with heavy contention, in order to
actually exercise ntsync's internal synchronization.

This test has several threads in a tight loop acquiring a mutex, modifying some
shared data, and then releasing the mutex. At the end we check if the data is
consistent.

Signed-off-by: Elizabeth Figura <zfigura@codeweavers.com>
Link: https://lore.kernel.org/r/20241213193511.457338-28-zfigura@codeweavers.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2025-01-08 13:18:12 +01:00

1343 lines
31 KiB
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