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