| // SPDX-License-Identifier: MIT |
| // Copyright © 2023 Simon Ser |
| |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <stdint.h> |
| #include <sys/eventfd.h> |
| |
| #include "drm.h" |
| #include "igt.h" |
| #include "igt_syncobj.h" |
| #include "sw_sync.h" |
| |
| /** |
| * TEST: syncobj eventfd |
| * Description: Tests for the drm sync object eventfd API |
| * Category: Core |
| * Mega feature: General Core features |
| * Sub-category: DRM |
| * Functionality: semaphore |
| * Feature: synchronization |
| * Test category: GEM_Legacy |
| */ |
| |
| IGT_TEST_DESCRIPTION("Tests for the drm sync object eventfd API"); |
| |
| static bool |
| has_syncobj_eventfd(int fd) |
| { |
| uint64_t value; |
| int ret; |
| |
| if (drmGetCap(fd, DRM_CAP_SYNCOBJ_TIMELINE, &value)) |
| return false; |
| if (!value) |
| return false; |
| |
| /* Try waiting with invalid syncobj should fail with ENOENT */ |
| ret = __syncobj_eventfd(fd, 0, 0, 0, -1); |
| return ret == -ENOENT; |
| } |
| |
| static int |
| syncobj_attach_sw_sync(int fd, uint32_t handle, uint64_t point) |
| { |
| int timeline, fence; |
| uint32_t syncobj; |
| |
| timeline = sw_sync_timeline_create(); |
| fence = sw_sync_timeline_create_fence(timeline, 1); |
| |
| if (point == 0) { |
| syncobj_import_sync_file(fd, handle, fence); |
| } else { |
| syncobj = syncobj_create(fd, 0); |
| |
| syncobj_import_sync_file(fd, syncobj, fence); |
| syncobj_binary_to_timeline(fd, handle, point, syncobj); |
| syncobj_destroy(fd, syncobj); |
| } |
| |
| close(fence); |
| |
| return timeline; |
| } |
| |
| static int |
| ev_fd_read(int ev_fd) |
| { |
| uint64_t ev_fd_value; |
| int ret; |
| |
| ret = read(ev_fd, &ev_fd_value, sizeof(ev_fd_value)); |
| if (ret == -1) |
| return -errno; |
| igt_assert_eq(ret, sizeof(ev_fd_value)); |
| return 0; |
| } |
| |
| static void |
| ev_fd_poll_in(int ev_fd, bool avail) |
| { |
| struct pollfd pollfd; |
| int ret; |
| int timeout_ms; |
| |
| /* Wait 5s if we're expecting data, 10ms otherwise */ |
| timeout_ms = avail ? 5000 : 10; |
| pollfd.fd = ev_fd; |
| pollfd.events = POLLIN; |
| pollfd.revents = 0; |
| ret = poll(&pollfd, 1, timeout_ms); |
| if (avail) { |
| igt_assert(ret >= 0); |
| igt_assert(pollfd.revents & POLLIN); |
| } else { |
| igt_assert_eq(ret, 0); |
| } |
| } |
| |
| static void |
| ev_fd_assert_unsignaled(int ev_fd) |
| { |
| /* Poll the eventfd to give the kernel time to signal it, error out if |
| * that happens */ |
| ev_fd_poll_in(ev_fd, false); |
| igt_assert_eq(ev_fd_read(ev_fd), -EAGAIN); |
| } |
| |
| static void |
| ev_fd_assert_signaled(int ev_fd) |
| { |
| ev_fd_poll_in(ev_fd, true); |
| igt_assert_eq(ev_fd_read(ev_fd), 0); |
| } |
| |
| static const char test_bad_flags_desc[] = |
| "Verifies that passing bad flags is rejected"; |
| static void |
| test_bad_flags(int fd) |
| { |
| uint32_t flags; |
| uint32_t syncobj; |
| int ev_fd; |
| |
| syncobj = syncobj_create(fd, DRM_SYNCOBJ_CREATE_SIGNALED); |
| flags = 0xdeadbeef; |
| ev_fd = eventfd(0, EFD_NONBLOCK); |
| igt_assert_eq(__syncobj_eventfd(fd, syncobj, 0, flags, ev_fd), -EINVAL); |
| |
| close(ev_fd); |
| syncobj_destroy(fd, syncobj); |
| } |
| |
| static const char test_illegal_handle_desc[] = |
| "Verifies that passing an invalid syncobj handle is rejected"; |
| static void |
| test_illegal_handle(int fd) |
| { |
| int ev_fd; |
| |
| ev_fd = eventfd(0, EFD_NONBLOCK); |
| igt_assert_eq(__syncobj_eventfd(fd, 0, 0, 0, ev_fd), -ENOENT); |
| |
| close(ev_fd); |
| } |
| |
| static const char test_illegal_eventfd_desc[] = |
| "Verifies that passing an invalid eventfd is rejected"; |
| static void |
| test_illegal_eventfd(int fd) |
| { |
| int dev_null; |
| uint32_t syncobj; |
| |
| syncobj = syncobj_create(fd, DRM_SYNCOBJ_CREATE_SIGNALED); |
| |
| dev_null = open("/dev/null", O_RDWR); |
| igt_assert(dev_null >= 0); |
| |
| igt_assert_eq(__syncobj_eventfd(fd, syncobj, 0, 0, dev_null), -EINVAL); |
| |
| close(dev_null); |
| syncobj_destroy(fd, syncobj); |
| } |
| |
| static const char test_bad_pad_desc[] = |
| "Verifies that passing a non-zero padding is rejected"; |
| static void |
| test_bad_pad(int fd) |
| { |
| struct drm_syncobj_eventfd args; |
| int ret; |
| |
| args.handle = syncobj_create(fd, DRM_SYNCOBJ_CREATE_SIGNALED); |
| args.flags = 0; |
| args.point = 0; |
| args.fd = eventfd(0, EFD_NONBLOCK); |
| args.pad = 0xdeadbeef; |
| |
| ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_EVENTFD, &args); |
| igt_assert(ret == -1 && errno == EINVAL); |
| } |
| |
| static const char test_wait_desc[] = |
| "Verifies waiting an already-materialized fence"; |
| static void |
| test_wait(int fd, bool use_timeline) |
| { |
| uint32_t syncobj; |
| int timeline, ev_fd_wait, ev_fd_avail; |
| uint64_t point = use_timeline ? 1 : 0; |
| |
| syncobj = syncobj_create(fd, 0); |
| timeline = syncobj_attach_sw_sync(fd, syncobj, point); |
| ev_fd_wait = eventfd(0, EFD_NONBLOCK); |
| ev_fd_avail = eventfd(0, EFD_NONBLOCK); |
| |
| syncobj_eventfd(fd, syncobj, point, 0, ev_fd_wait); |
| syncobj_eventfd(fd, syncobj, point, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, |
| ev_fd_avail); |
| |
| ev_fd_assert_unsignaled(ev_fd_wait); |
| ev_fd_assert_signaled(ev_fd_avail); |
| |
| sw_sync_timeline_inc(timeline, 1); |
| |
| ev_fd_assert_signaled(ev_fd_wait); |
| |
| close(ev_fd_wait); |
| close(ev_fd_avail); |
| close(timeline); |
| syncobj_destroy(fd, syncobj); |
| } |
| |
| static const char test_wait_before_signal_desc[] = |
| "Verifies waiting a fence not yet materialized"; |
| static void |
| test_wait_before_signal(int fd, bool use_timeline) |
| { |
| uint32_t syncobj; |
| int timeline, ev_fd_wait, ev_fd_avail; |
| uint64_t point = use_timeline ? 1 : 0; |
| |
| syncobj = syncobj_create(fd, 0); |
| ev_fd_wait = eventfd(0, EFD_NONBLOCK); |
| ev_fd_avail = eventfd(0, EFD_NONBLOCK); |
| |
| syncobj_eventfd(fd, syncobj, point, 0, ev_fd_wait); |
| syncobj_eventfd(fd, syncobj, point, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, |
| ev_fd_avail); |
| |
| ev_fd_assert_unsignaled(ev_fd_wait); |
| ev_fd_assert_unsignaled(ev_fd_avail); |
| |
| timeline = syncobj_attach_sw_sync(fd, syncobj, point); |
| |
| ev_fd_assert_unsignaled(ev_fd_wait); |
| ev_fd_assert_signaled(ev_fd_avail); |
| |
| sw_sync_timeline_inc(timeline, 1); |
| |
| ev_fd_assert_signaled(ev_fd_wait); |
| |
| close(ev_fd_wait); |
| close(ev_fd_avail); |
| close(timeline); |
| syncobj_destroy(fd, syncobj); |
| } |
| |
| static const char test_wait_signaled_desc[] = |
| "Verifies waiting an already-signaled fence"; |
| static void |
| test_wait_signaled(int fd, bool use_timeline) |
| { |
| uint32_t syncobj; |
| int timeline, ev_fd_wait, ev_fd_avail; |
| uint64_t point = use_timeline ? 1 : 0; |
| |
| syncobj = syncobj_create(fd, 0); |
| ev_fd_wait = eventfd(0, EFD_NONBLOCK); |
| ev_fd_avail = eventfd(0, EFD_NONBLOCK); |
| |
| timeline = syncobj_attach_sw_sync(fd, syncobj, point); |
| sw_sync_timeline_inc(timeline, 1); |
| |
| syncobj_eventfd(fd, syncobj, point, 0, ev_fd_wait); |
| syncobj_eventfd(fd, syncobj, point, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, |
| ev_fd_avail); |
| |
| ev_fd_assert_signaled(ev_fd_wait); |
| ev_fd_assert_signaled(ev_fd_avail); |
| |
| close(ev_fd_wait); |
| close(ev_fd_avail); |
| close(timeline); |
| syncobj_destroy(fd, syncobj); |
| } |
| |
| igt_main |
| { |
| int fd = -1, i; |
| |
| igt_fixture { |
| fd = drm_open_driver(DRIVER_ANY); |
| igt_require(has_syncobj_eventfd(fd)); |
| igt_require_sw_sync(); |
| } |
| |
| igt_describe(test_bad_flags_desc); |
| igt_subtest("invalid-bad-flags") |
| test_bad_flags(fd); |
| |
| igt_describe(test_illegal_handle_desc); |
| igt_subtest("invalid-illegal-handle") |
| test_illegal_handle(fd); |
| |
| igt_describe(test_illegal_eventfd_desc); |
| igt_subtest("invalid-illegal-eventfd") |
| test_illegal_eventfd(fd); |
| |
| igt_describe(test_bad_pad_desc); |
| igt_subtest("invalid-bad-pad") |
| test_bad_pad(fd); |
| |
| for (i = 0; i < 2; i++) { |
| bool use_timeline = i == 1; |
| const char *kind = use_timeline ? "timeline" : "binary"; |
| |
| igt_describe(test_wait_desc); |
| igt_subtest_f("%s-wait", kind) |
| test_wait(fd, use_timeline); |
| |
| igt_describe(test_wait_before_signal_desc); |
| igt_subtest_f("%s-wait-before-signal", kind) |
| test_wait_before_signal(fd, use_timeline); |
| |
| igt_describe(test_wait_signaled_desc); |
| igt_subtest_f("%s-wait-signaled", kind) |
| test_wait_signaled(fd, use_timeline); |
| } |
| |
| igt_fixture { |
| drm_close_driver(fd); |
| } |
| } |