xref: /linux/tools/testing/selftests/futex/functional/robust_list.c (revision 59e6295fac26b8e85c1ea859cdd89fa1e47519d7)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2025 Igalia S.L.
4  *
5  * Robust list test by André Almeida <andrealmeid@igalia.com>
6  *
7  * The robust list uAPI allows userspace to create "robust" locks, in the sense
8  * that if the lock holder thread dies, the remaining threads that are waiting
9  * for the lock won't block forever, waiting for a lock that will never be
10  * released.
11  *
12  * This is achieve by userspace setting a list where a thread can enter all the
13  * locks (futexes) that it is holding. The robust list is a linked list, and
14  * userspace register the start of the list with the syscall set_robust_list().
15  * If such thread eventually dies, the kernel will walk this list, waking up one
16  * thread waiting for each futex and marking the futex word with the flag
17  * FUTEX_OWNER_DIED.
18  *
19  * See also
20  *	man set_robust_list
21  *	Documententation/locking/robust-futex-ABI.rst
22  *	Documententation/locking/robust-futexes.rst
23  */
24 
25 #define _GNU_SOURCE
26 
27 #include "futextest.h"
28 #include "kselftest_harness.h"
29 
30 #include <dlfcn.h>
31 #include <errno.h>
32 #include <pthread.h>
33 #include <signal.h>
34 #include <stdatomic.h>
35 #include <stdbool.h>
36 #include <stddef.h>
37 #include <stdint.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/auxv.h>
41 #include <sys/mman.h>
42 #include <sys/wait.h>
43 
44 #define STACK_SIZE (1024 * 1024)
45 #define FUTEX_TIMEOUT 3
46 #define SLEEP_US 100
47 
48 #if __SIZEOF_LONG__ == 8
49 # define BUILD_64
50 #endif
51 
52 static pthread_barrier_t barrier, barrier2;
53 
54 static int set_robust_list(struct robust_list_head *head, size_t len)
55 {
56 	return syscall(SYS_set_robust_list, head, len);
57 }
58 
59 static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ptr)
60 {
61 	return syscall(SYS_get_robust_list, pid, head, len_ptr);
62 }
63 
64 static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, int val,
65 				   void *list_op_pending, unsigned int val3)
66 {
67 	return syscall(SYS_futex, uaddr, op, val, NULL, list_op_pending, val3, 0);
68 }
69 
70 /*
71  * Basic lock struct, contains just the futex word and the robust list element
72  * Real implementations have also a *prev to easily walk in the list
73  */
74 typedef _Atomic(unsigned int) atomic_futex_t;
75 
76 struct lock_struct {
77 	atomic_futex_t		futex;
78 	struct robust_list	list;
79 };
80 
81 struct child_args {
82 	struct __test_metadata	*_metadata;
83 	void			*arg;
84 };
85 
86 /*
87  * Helper function to spawn a child thread. Returns -1 on error, pid on success
88  */
89 static int create_child(struct __test_metadata *_metadata, int (*fn)(void *arg), void *arg)
90 {
91 	struct child_args *cargs = malloc(sizeof(*cargs));
92 	char *stack;
93 	pid_t pid;
94 
95 	if (!cargs)
96 		return -1;
97 	cargs->_metadata = _metadata;
98 	cargs->arg = arg;
99 
100 	stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
101 		     MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
102 	if (stack == MAP_FAILED) {
103 		free(cargs);
104 		return -1;
105 	}
106 
107 	stack += STACK_SIZE;
108 
109 	pid = clone(fn, stack, CLONE_VM | SIGCHLD, cargs);
110 	if (pid == -1) {
111 		free(cargs);
112 		return -1;
113 	}
114 
115 	return pid;
116 }
117 
118 /*
119  * Helper function to prepare and register a robust list
120  */
121 static int set_list(struct robust_list_head *head)
122 {
123 	int ret;
124 
125 	ret = set_robust_list(head, sizeof(*head));
126 	if (ret)
127 		return ret;
128 
129 	head->futex_offset = (size_t) offsetof(struct lock_struct, futex) -
130 			     (size_t) offsetof(struct lock_struct, list);
131 	head->list.next = &head->list;
132 	head->list_op_pending = NULL;
133 
134 	return 0;
135 }
136 
137 /*
138  * A basic (and incomplete) mutex lock function with robustness
139  */
140 static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject)
141 {
142 	atomic_futex_t *futex = &lock->futex;
143 	unsigned int zero = 0;
144 	pid_t tid = gettid();
145 	int ret = -1;
146 
147 	/*
148 	 * Set list_op_pending before starting the lock, so the kernel can catch
149 	 * the case where the thread died during the lock operation
150 	 */
151 	head->list_op_pending = &lock->list;
152 
153 	if (atomic_compare_exchange_strong(futex, &zero, tid)) {
154 		/*
155 		 * We took the lock, insert it in the robust list
156 		 */
157 		struct robust_list *list = &head->list;
158 
159 		/* Error injection to test list_op_pending */
160 		if (error_inject)
161 			return 0;
162 
163 		while (list->next != &head->list)
164 			list = list->next;
165 
166 		list->next = &lock->list;
167 		lock->list.next = &head->list;
168 
169 		ret = 0;
170 	} else {
171 		/*
172 		 * We didn't take the lock, wait until the owner wakes (or dies)
173 		 */
174 		struct timespec to;
175 
176 		to.tv_sec = FUTEX_TIMEOUT;
177 		to.tv_nsec = 0;
178 
179 		tid = atomic_load(futex);
180 		/* Kernel ignores futexes without the waiters flag */
181 		tid |= FUTEX_WAITERS;
182 		atomic_store(futex, tid);
183 
184 		ret = futex_wait((futex_t *) futex, tid, &to, 0);
185 
186 		/*
187 		 * A real mutex_lock() implementation would loop here to finally
188 		 * take the lock. We don't care about that, so we stop here.
189 		 */
190 	}
191 
192 	head->list_op_pending = NULL;
193 
194 	return ret;
195 }
196 
197 /*
198  * This child thread will succeed taking the lock, and then will exit holding it
199  */
200 static int child_fn_lock(void *arg)
201 {
202 	struct child_args *cargs = arg;
203 	struct __test_metadata *_metadata = cargs->_metadata;
204 	struct lock_struct *lock = cargs->arg;
205 	struct robust_list_head head;
206 	int ret;
207 
208 	free(cargs);
209 
210 	ret = set_list(&head);
211 	ASSERT_EQ(ret, 0)
212 		TH_LOG("set_robust_list error");
213 
214 	ret = mutex_lock(lock, &head, false);
215 	ASSERT_EQ(ret, 0)
216 		TH_LOG("mutex_lock error");
217 
218 	pthread_barrier_wait(&barrier);
219 
220 	/*
221 	 * There's a race here: the parent thread needs to be inside
222 	 * futex_wait() before the child thread dies, otherwise it will miss the
223 	 * wakeup from handle_futex_death() that this child will emit. We wait a
224 	 * little bit just to make sure that this happens.
225 	 */
226 	usleep(SLEEP_US);
227 
228 	return 0;
229 }
230 
231 /*
232  * Spawns a child thread that will set a robust list, take the lock, register it
233  * in the robust list and die. The parent thread will wait on this futex, and
234  * should be waken up when the child exits.
235  */
236 TEST(test_robustness)
237 {
238 	struct lock_struct lock = { .futex = 0 };
239 	atomic_futex_t *futex = &lock.futex;
240 	struct robust_list_head head;
241 	int ret, pid, wstatus;
242 
243 	ret = set_list(&head);
244 	ASSERT_EQ(ret, 0);
245 
246 	/*
247 	 * Lets use a barrier to ensure that the child thread takes the lock
248 	 * before the parent
249 	 */
250 	ret = pthread_barrier_init(&barrier, NULL, 2);
251 	ASSERT_EQ(ret, 0);
252 
253 	pid = create_child(_metadata, &child_fn_lock, &lock);
254 	ASSERT_NE(pid, -1);
255 
256 	pthread_barrier_wait(&barrier);
257 	ret = mutex_lock(&lock, &head, false);
258 
259 	/*
260 	 * futex_wait() should return 0 and the futex word should be marked with
261 	 * FUTEX_OWNER_DIED
262 	 */
263 	ASSERT_EQ(ret, 0);
264 
265 	ASSERT_TRUE(*futex & FUTEX_OWNER_DIED);
266 
267 	wait(&wstatus);
268 	pthread_barrier_destroy(&barrier);
269 
270 	EXPECT_EQ(WEXITSTATUS(wstatus), 0)
271 		TH_LOG("child failed");
272 }
273 
274 /*
275  * The only valid value for len is sizeof(*head)
276  */
277 TEST(test_set_robust_list_invalid_size)
278 {
279 	struct robust_list_head head;
280 	size_t head_size = sizeof(head);
281 	int ret;
282 
283 	ret = set_robust_list(&head, head_size);
284 	ASSERT_EQ(ret, 0);
285 
286 	ret = set_robust_list(&head, head_size * 2);
287 	ASSERT_EQ(ret, -1);
288 	ASSERT_EQ(errno, EINVAL);
289 
290 	ret = set_robust_list(&head, head_size - 1);
291 	ASSERT_EQ(ret, -1);
292 	ASSERT_EQ(errno, EINVAL);
293 
294 	ret = set_robust_list(&head, 0);
295 	ASSERT_EQ(ret, -1);
296 	ASSERT_EQ(errno, EINVAL);
297 }
298 
299 /*
300  * Test get_robust_list with pid = 0, getting the list of the running thread
301  */
302 TEST(test_get_robust_list_self)
303 {
304 	struct robust_list_head head, head2, *get_head;
305 	size_t head_size = sizeof(head), len_ptr;
306 	int ret;
307 
308 	ret = set_robust_list(&head, head_size);
309 	ASSERT_EQ(ret, 0);
310 
311 	ret = get_robust_list(0, &get_head, &len_ptr);
312 	ASSERT_EQ(ret, 0);
313 	ASSERT_EQ(get_head, &head);
314 	ASSERT_EQ(head_size, len_ptr);
315 
316 	ret = set_robust_list(&head2, head_size);
317 	ASSERT_EQ(ret, 0);
318 
319 	ret = get_robust_list(0, &get_head, &len_ptr);
320 	ASSERT_EQ(ret, 0);
321 	ASSERT_EQ(get_head, &head2);
322 	ASSERT_EQ(head_size, len_ptr);
323 }
324 
325 static int child_list(void *arg)
326 {
327 	struct child_args *cargs = arg;
328 	struct __test_metadata *_metadata = cargs->_metadata;
329 	struct robust_list_head *head = cargs->arg;
330 	int ret;
331 
332 	free(cargs);
333 
334 	ret = set_robust_list(head, sizeof(*head));
335 	ASSERT_EQ(ret, 0)
336 		TH_LOG("set_robust_list error");
337 
338 	/*
339 	 * After setting the list head, wait until the main thread can call
340 	 * get_robust_list() for this thread before exiting.
341 	 */
342 	pthread_barrier_wait(&barrier);
343 	pthread_barrier_wait(&barrier2);
344 
345 	return 0;
346 }
347 
348 /*
349  * Test get_robust_list from another thread. We use two barriers here to ensure
350  * that:
351  *   1) the child thread set the list before we try to get it from the
352  * parent
353  *   2) the child thread still alive when we try to get the list from it
354  */
355 TEST(test_get_robust_list_child)
356 {
357 	struct robust_list_head head, *get_head;
358 	int ret, wstatus;
359 	size_t len_ptr;
360 	pid_t tid;
361 
362 	ret = pthread_barrier_init(&barrier, NULL, 2);
363 	ret = pthread_barrier_init(&barrier2, NULL, 2);
364 	ASSERT_EQ(ret, 0);
365 
366 	tid = create_child(_metadata, &child_list, &head);
367 	ASSERT_NE(tid, -1);
368 
369 	pthread_barrier_wait(&barrier);
370 
371 	ret = get_robust_list(tid, &get_head, &len_ptr);
372 	ASSERT_EQ(ret, 0);
373 	ASSERT_EQ(&head, get_head);
374 
375 	pthread_barrier_wait(&barrier2);
376 
377 	wait(&wstatus);
378 	pthread_barrier_destroy(&barrier);
379 	pthread_barrier_destroy(&barrier2);
380 
381 	EXPECT_EQ(WEXITSTATUS(wstatus), 0)
382 		TH_LOG("child failed");
383 }
384 
385 static int child_fn_lock_with_error(void *arg)
386 {
387 	struct child_args *cargs = arg;
388 	struct __test_metadata *_metadata = cargs->_metadata;
389 	struct lock_struct *lock = cargs->arg;
390 	struct robust_list_head head;
391 	int ret;
392 
393 	free(cargs);
394 
395 	ret = set_list(&head);
396 	ASSERT_EQ(ret, 0)
397 		TH_LOG("set_robust_list error");
398 
399 	ret = mutex_lock(lock, &head, true);
400 	ASSERT_EQ(ret, 0)
401 		TH_LOG("mutex_lock error");
402 
403 	pthread_barrier_wait(&barrier);
404 
405 	/* See comment at child_fn_lock() */
406 	usleep(SLEEP_US);
407 
408 	return 0;
409 }
410 
411 /*
412  * Same as robustness test, but inject an error where the mutex_lock() exits
413  * earlier, just after setting list_op_pending and taking the lock, to test the
414  * list_op_pending mechanism
415  */
416 TEST(test_set_list_op_pending)
417 {
418 	struct lock_struct lock = { .futex = 0 };
419 	atomic_futex_t *futex = &lock.futex;
420 	struct robust_list_head head;
421 	int ret, wstatus;
422 
423 	ret = set_list(&head);
424 	ASSERT_EQ(ret, 0);
425 
426 	ret = pthread_barrier_init(&barrier, NULL, 2);
427 	ASSERT_EQ(ret, 0);
428 
429 	ret = create_child(_metadata, &child_fn_lock_with_error, &lock);
430 	ASSERT_NE(ret, -1);
431 
432 	pthread_barrier_wait(&barrier);
433 	ret = mutex_lock(&lock, &head, false);
434 
435 	ASSERT_EQ(ret, 0);
436 
437 	ASSERT_TRUE(*futex & FUTEX_OWNER_DIED);
438 
439 	wait(&wstatus);
440 	pthread_barrier_destroy(&barrier);
441 
442 	EXPECT_EQ(WEXITSTATUS(wstatus), 0)
443 		TH_LOG("child failed");
444 }
445 
446 #define CHILD_NR 10
447 
448 static int child_lock_holder(void *arg)
449 {
450 	struct child_args *cargs = arg;
451 	struct lock_struct *locks = cargs->arg;
452 	struct robust_list_head head;
453 	int i;
454 
455 	free(cargs);
456 
457 	set_list(&head);
458 
459 	for (i = 0; i < CHILD_NR; i++) {
460 		locks[i].futex = 0;
461 		mutex_lock(&locks[i], &head, false);
462 	}
463 
464 	pthread_barrier_wait(&barrier);
465 	pthread_barrier_wait(&barrier2);
466 
467 	/* See comment at child_fn_lock() */
468 	usleep(SLEEP_US);
469 
470 	return 0;
471 }
472 
473 static int child_wait_lock(void *arg)
474 {
475 	struct child_args *cargs = arg;
476 	struct __test_metadata *_metadata = cargs->_metadata;
477 	struct lock_struct *lock = cargs->arg;
478 	struct robust_list_head head;
479 	int ret;
480 
481 	free(cargs);
482 
483 	pthread_barrier_wait(&barrier2);
484 	ret = mutex_lock(lock, &head, false);
485 	ASSERT_EQ(ret, 0)
486 		TH_LOG("mutex_lock error");
487 
488 	ASSERT_TRUE(lock->futex & FUTEX_OWNER_DIED)
489 		TH_LOG("futex not marked with FUTEX_OWNER_DIED");
490 
491 	return 0;
492 }
493 
494 /*
495  * Test a robust list of more than one element. All the waiters should wake when
496  * the holder dies
497  */
498 TEST(test_robust_list_multiple_elements)
499 {
500 	struct lock_struct locks[CHILD_NR];
501 	pid_t pids[CHILD_NR + 1];
502 	int i, ret, wstatus;
503 
504 	ret = pthread_barrier_init(&barrier, NULL, 2);
505 	ASSERT_EQ(ret, 0);
506 	ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1);
507 	ASSERT_EQ(ret, 0);
508 
509 	pids[0] = create_child(_metadata, &child_lock_holder, &locks);
510 	ASSERT_NE(pids[0], -1);
511 
512 	/* Wait until the locker thread takes the look */
513 	pthread_barrier_wait(&barrier);
514 
515 	for (i = 0; i < CHILD_NR; i++) {
516 		pids[i+1] = create_child(_metadata, &child_wait_lock, &locks[i]);
517 		ASSERT_NE(pids[i+1], -1);
518 	}
519 
520 	/* Wait for all children to return (holder + all waiters) */
521 	ret = 0;
522 	for (i = 0; i < CHILD_NR + 1; i++) {
523 		waitpid(pids[i], &wstatus, 0);
524 		if (WEXITSTATUS(wstatus))
525 			ret = -1;
526 	}
527 
528 	pthread_barrier_destroy(&barrier);
529 	pthread_barrier_destroy(&barrier2);
530 
531 	EXPECT_EQ(ret, 0)
532 		TH_LOG("One or more children failed");
533 }
534 
535 static int child_circular_list(void *arg)
536 {
537 	struct child_args *cargs = arg;
538 	struct __test_metadata *_metadata = cargs->_metadata;
539 	static struct lock_struct a, b, c;
540 	struct robust_list_head head;
541 	int ret;
542 
543 	free(cargs);
544 
545 	ret = set_list(&head);
546 	ASSERT_EQ(ret, 0)
547 		TH_LOG("set_list error");
548 
549 	head.list.next = &a.list;
550 
551 	/*
552 	 * The last element should point to head list, but we short circuit it
553 	 */
554 	a.list.next = &b.list;
555 	b.list.next = &c.list;
556 	c.list.next = &a.list;
557 
558 	return 0;
559 }
560 
561 /*
562  * Create a circular robust list. The kernel should be able to destroy the list
563  * while processing it so it won't be trapped in an infinite loop while handling
564  * a process exit
565  */
566 TEST(test_circular_list)
567 {
568 	int wstatus;
569 	pid_t pid;
570 
571 	pid = create_child(_metadata, child_circular_list, NULL);
572 	ASSERT_NE(pid, -1);
573 
574 	wait(&wstatus);
575 
576 	EXPECT_EQ(WEXITSTATUS(wstatus), 0)
577 		TH_LOG("child failed");
578 }
579 
580 /*
581  * Below are tests for the fix of robust release race condition. Please read the following
582  * thread to learn more about the issue in the first place and why the following functions fix it:
583  * https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/
584  */
585 
586 /*
587  * Auxiliary code for binding the vDSO functions
588  */
589 static void *get_vdso_func_addr(const char *function)
590 {
591 	const char *vdso_names[] = {
592 		"linux-vdso.so.1", "linux-gate.so.1", "linux-vdso32.so.1", "linux-vdso64.so.1",
593 	};
594 
595 	for (int i = 0; i < ARRAY_SIZE(vdso_names); i++) {
596 		void *vdso = dlopen(vdso_names[i], RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
597 
598 		if (vdso)
599 			return dlsym(vdso, function);
600 	}
601 	return NULL;
602 }
603 
604 /*
605  * These are the real vDSO function signatures:
606  *
607  *	__vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop)
608  *	__vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop)
609  *
610  * So for the generic entry point we need to use a void pointer as the last argument
611  */
612 FIXTURE(vdso_unlock)
613 {
614 	uint32_t (*vdso)(_Atomic(uint32_t) *lock, uint32_t tid, void *pop);
615 };
616 
617 FIXTURE_VARIANT(vdso_unlock)
618 {
619 	bool is_32;
620 	char func_name[];
621 };
622 
623 FIXTURE_SETUP(vdso_unlock)
624 {
625 	self->vdso = get_vdso_func_addr(variant->func_name);
626 }
627 
628 FIXTURE_TEARDOWN(vdso_unlock) {}
629 
630 FIXTURE_VARIANT_ADD(vdso_unlock, 32)
631 {
632 	.func_name = "__vdso_futex_robust_list32_try_unlock",
633 	.is_32 = true,
634 };
635 
636 FIXTURE_VARIANT_ADD(vdso_unlock, 64)
637 {
638 	.func_name = "__vdso_futex_robust_list64_try_unlock",
639 	.is_32 = false,
640 };
641 
642 /*
643  * Test the vDSO robust_listXX_try_unlock() for the uncontended case. The virtual syscall should
644  * return the thread ID of the lock owner, the lock word must be 0 and the list_op_pending should
645  * be NULL.
646  */
647 TEST_F(vdso_unlock, test_robust_try_unlock_uncontended)
648 {
649 	struct lock_struct lock = { .futex = 0 };
650 	_Atomic(unsigned int) *futex = &lock.futex;
651 	struct robust_list_head head;
652 	uintptr_t exp = (uintptr_t) NULL;
653 	pid_t tid = gettid();
654 	int ret;
655 
656 	if (!self->vdso) {
657 		ksft_test_result_skip("%s not found\n", variant->func_name);
658 		return;
659 	}
660 
661 	*futex = tid;
662 
663 	ret = set_list(&head);
664 	if (ret)
665 		ksft_test_result_fail("set_robust_list error\n");
666 
667 	head.list_op_pending = &lock.list;
668 
669 	ret = self->vdso(futex, tid, &head.list_op_pending);
670 
671 	ASSERT_EQ(ret, tid);
672 	ASSERT_EQ(*futex, 0);
673 
674 	/* Check only the lower 32 bits for the 32-bit entry point */
675 	if (variant->is_32) {
676 		exp = (uintptr_t)(unsigned long)&lock.list;
677 		exp &= ~0xFFFFFFFFULL;
678 	}
679 
680 	ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp);
681 }
682 
683 /*
684  * If the lock is contended, the operation fails. The return value is the value found at the
685  * futex word (tid | FUTEX_WAITERS), the futex word is not modified and the list_op_pending is_32
686  * not cleared.
687  */
688 TEST_F(vdso_unlock, test_robust_try_unlock_contended)
689 {
690 	struct lock_struct lock = { .futex = 0 };
691 	_Atomic(unsigned int) *futex = &lock.futex;
692 	struct robust_list_head head;
693 	pid_t tid = gettid();
694 	int ret;
695 
696 	if (!self->vdso) {
697 		ksft_test_result_skip("%s not found\n", variant->func_name);
698 		return;
699 	}
700 
701 	*futex = tid | FUTEX_WAITERS;
702 
703 	ret = set_list(&head);
704 	if (ret)
705 		ksft_test_result_fail("set_robust_list error\n");
706 
707 	head.list_op_pending = &lock.list;
708 
709 	ret = self->vdso(futex, tid, &head.list_op_pending);
710 
711 	ASSERT_EQ(ret, tid | FUTEX_WAITERS);
712 	ASSERT_EQ(*futex, tid | FUTEX_WAITERS);
713 	ASSERT_EQ(head.list_op_pending, &lock.list);
714 }
715 
716 FIXTURE(futex_op) {};
717 
718 FIXTURE_VARIANT(futex_op)
719 {
720 	unsigned int op;
721 	unsigned int val3;
722 };
723 
724 FIXTURE_SETUP(futex_op) {}
725 
726 FIXTURE_TEARDOWN(futex_op) {}
727 
728 FIXTURE_VARIANT_ADD(futex_op, wake)
729 {
730 	.op = FUTEX_WAKE,
731 	.val3 = 0,
732 };
733 
734 FIXTURE_VARIANT_ADD(futex_op, wake_bitset)
735 {
736 	.op = FUTEX_WAKE_BITSET,
737 	.val3 = FUTEX_BITSET_MATCH_ANY,
738 };
739 
740 FIXTURE_VARIANT_ADD(futex_op, unlock_pi)
741 {
742 	.op = FUTEX_UNLOCK_PI,
743 	.val3 = 0,
744 };
745 
746 FIXTURE_VARIANT_ADD(futex_op, wake32)
747 {
748 	.op = FUTEX_WAKE | FUTEX_ROBUST_LIST32,
749 	.val3 = 0,
750 };
751 
752 FIXTURE_VARIANT_ADD(futex_op, wake_bitset32)
753 {
754 	.op = FUTEX_WAKE_BITSET | FUTEX_ROBUST_LIST32,
755 	.val3 = FUTEX_BITSET_MATCH_ANY,
756 };
757 
758 FIXTURE_VARIANT_ADD(futex_op, unlock_pi32)
759 {
760 	.op = FUTEX_UNLOCK_PI | FUTEX_ROBUST_LIST32,
761 	.val3 = 0,
762 };
763 
764 /*
765  * The syscall should return the number of tasks waken (for this test, 0), clear the futex word and
766  * clear list_op_pending
767  */
768 TEST_F(futex_op, test_futex_robust_unlock)
769 {
770 	struct lock_struct lock = { .futex = 0 };
771 	_Atomic(unsigned int) *futex = &lock.futex;
772 	uintptr_t exp = (uintptr_t) NULL;
773 	struct robust_list_head head;
774 	pid_t tid = gettid();
775 	int ret;
776 
777 #ifndef BUILD_64
778 	if (!(variant->op & FUTEX_ROBUST_LIST32)) {
779 		ksft_test_result_skip("Not supported for 32 bit build\n");
780 		return;
781 	}
782 #endif
783 
784 	*futex = tid | FUTEX_WAITERS;
785 
786 	ret = set_list(&head);
787 	if (ret)
788 		ksft_test_result_fail("set_robust_list error\n");
789 
790 	head.list_op_pending = &lock.list;
791 
792 	ret = sys_futex_robust_unlock(futex, FUTEX_ROBUST_UNLOCK | variant->op, tid,
793 				      &head.list_op_pending, variant->val3);
794 
795 	ASSERT_EQ(ret, 0);
796 	ASSERT_EQ(*futex, 0);
797 
798 	if (variant->op & FUTEX_ROBUST_LIST32) {
799 		exp = (uint64_t)(unsigned long)&lock.list;
800 		exp &= ~0xFFFFFFFFULL;
801 	}
802 
803 	ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp);
804 }
805 
806 TEST_HARNESS_MAIN
807