// SPDX-License-Identifier: GPL-2.0-or-later /* * Various unit tests for the "ntsync" synchronization primitive driver. * * Copyright (C) 2021-2022 Elizabeth Figura */ #define _GNU_SOURCE #include #include #include #include #include #include #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