| /* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| #ifndef _FUTEX_THREAD_H | |
| #define _FUTEX_THREAD_H | |
| #include <errno.h> | |
| #include <pthread.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include "kselftest_harness.h" | |
| #define USEC_PER_SEC 1000000L | |
| #define WAIT_FOR_THREAD_SECS 1 | |
| #define WAIT_FOR_THREAD_USECS (WAIT_FOR_THREAD_SECS * USEC_PER_SEC) | |
| #define WAIT_THREAD_RETRIES 100 | |
| struct futex_thread { | |
| pthread_t thread; | |
| pthread_barrier_t barrier; | |
| pid_t tid; | |
| int (*threadfn)(void *arg); | |
| void *arg; | |
| int retval; | |
| }; | |
| static inline int __wait_for_thread(FILE *fp, struct __test_metadata *_metadata) | |
| { | |
| unsigned int sleep_time_us = WAIT_FOR_THREAD_USECS / WAIT_THREAD_RETRIES; | |
| char buf[80] = ""; | |
| for (int i = 0; i < WAIT_THREAD_RETRIES; i++) { | |
| if (!fgets(buf, sizeof(buf), fp)) | |
| return EIO; | |
| if (!strncmp(buf, "futex", 5)) | |
| return 0; | |
| usleep(sleep_time_us); | |
| rewind(fp); | |
| } | |
| TH_LOG("/proc/$PID/wchan contains \"%s\". Trying to continue.", buf); | |
| return 0; | |
| } | |
| static void *__futex_thread_fn(void *arg) | |
| { | |
| struct futex_thread *t = arg; | |
| t->tid = gettid(); | |
| pthread_barrier_wait(&t->barrier); | |
| t->retval = t->threadfn(t->arg); | |
| return NULL; | |
| } | |
| /** | |
| * futex_wait_for_thread - Wait for the child thread to sleep in the futex context | |
| * @t: Thread handle. | |
| * @_metadata: Test metadata for TH_LOG() context | |
| */ | |
| static inline int futex_wait_for_thread(struct futex_thread *t, struct __test_metadata *_metadata) | |
| { | |
| char fname[80]; | |
| FILE *fp; | |
| int res; | |
| snprintf(fname, sizeof(fname), "/proc/%d/wchan", t->tid); | |
| fp = fopen(fname, "r"); | |
| if (!fp) { | |
| /* If /proc/... is not available, sleep */ | |
| if (errno != ENOENT) | |
| return errno; | |
| TH_LOG("/proc/$PID/wchan not accessible, continue with sleep()"); | |
| sleep(WAIT_FOR_THREAD_SECS); | |
| return 0; | |
| } | |
| res = __wait_for_thread(fp, _metadata); | |
| fclose(fp); | |
| return res; | |
| } | |
| /** | |
| * futex_thread_create - Create a new thread for testing. | |
| * @t: The handle of the newly created thread. | |
| * @threadfn: The new thread starts execution by invoking threadfn | |
| * @arg: The parameters passed to threadfn. | |
| */ | |
| static inline int futex_thread_create(struct futex_thread *t, int (*threadfn)(void *), void *arg) | |
| { | |
| pthread_barrier_init(&t->barrier, NULL, 2); | |
| t->tid = 0; | |
| t->threadfn = threadfn; | |
| t->arg = arg; | |
| if (pthread_create(&t->thread, NULL, __futex_thread_fn, t) < 0) { | |
| int ret = errno; | |
| pthread_barrier_destroy(&t->barrier); | |
| return ret; | |
| } | |
| pthread_barrier_wait(&t->barrier); | |
| return 0; | |
| } | |
| /** | |
| * futex_thread_destroy - Wait for and reclaim the resources of the thread. | |
| * @t: Thread handle. | |
| */ | |
| static inline int futex_thread_destroy(struct futex_thread *t) | |
| { | |
| pthread_join(t->thread, NULL); | |
| pthread_barrier_destroy(&t->barrier); | |
| return t->retval; | |
| } | |
| #endif |