/* * Copyright © 2012-2013 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authors: * Daniel Vetter * */ /* * Testcase: Check whether prime import/export works on the same device * * ... but with different fds, i.e. the wayland usecase. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "drm.h" #include "i915_drm.h" #include "drmtest.h" #define BO_SIZE (16*1024) static char counter; volatile int pls_die = 0; static void check_bo(int fd1, uint32_t handle1, int fd2, uint32_t handle2) { char *ptr1, *ptr2; int i; ptr1 = gem_mmap(fd1, handle1, BO_SIZE, PROT_READ | PROT_WRITE); ptr2 = gem_mmap(fd2, handle2, BO_SIZE, PROT_READ | PROT_WRITE); igt_assert(ptr1); /* check whether it's still our old object first. */ for (i = 0; i < BO_SIZE; i++) { igt_assert(ptr1[i] == counter); igt_assert(ptr2[i] == counter); } counter++; memset(ptr1, counter, BO_SIZE); igt_assert(memcmp(ptr1, ptr2, BO_SIZE) == 0); munmap(ptr1, BO_SIZE); munmap(ptr2, BO_SIZE); } static void test_with_fd_dup(void) { int fd1, fd2; uint32_t handle, handle_import; int dma_buf_fd1, dma_buf_fd2; counter = 0; fd1 = drm_open_any(); fd2 = drm_open_any(); handle = gem_create(fd1, BO_SIZE); dma_buf_fd1 = prime_handle_to_fd(fd1, handle); gem_close(fd1, handle); dma_buf_fd2 = dup(dma_buf_fd1); close(dma_buf_fd1); handle_import = prime_fd_to_handle(fd2, dma_buf_fd2); check_bo(fd2, handle_import, fd2, handle_import); close(dma_buf_fd2); check_bo(fd2, handle_import, fd2, handle_import); close(fd1); close(fd2); } static void test_with_two_bos(void) { int fd1, fd2; uint32_t handle1, handle2, handle_import; int dma_buf_fd; counter = 0; fd1 = drm_open_any(); fd2 = drm_open_any(); handle1 = gem_create(fd1, BO_SIZE); handle2 = gem_create(fd1, BO_SIZE); dma_buf_fd = prime_handle_to_fd(fd1, handle1); handle_import = prime_fd_to_handle(fd2, dma_buf_fd); close(dma_buf_fd); gem_close(fd1, handle1); dma_buf_fd = prime_handle_to_fd(fd1, handle2); handle_import = prime_fd_to_handle(fd2, dma_buf_fd); check_bo(fd1, handle2, fd2, handle_import); gem_close(fd1, handle2); close(dma_buf_fd); check_bo(fd2, handle_import, fd2, handle_import); close(fd1); close(fd2); } static void test_with_one_bo_two_files(void) { int fd1, fd2; uint32_t handle_import, handle_open, handle_orig, flink_name; int dma_buf_fd1, dma_buf_fd2; fd1 = drm_open_any(); fd2 = drm_open_any(); handle_orig = gem_create(fd1, BO_SIZE); dma_buf_fd1 = prime_handle_to_fd(fd1, handle_orig); flink_name = gem_flink(fd1, handle_orig); handle_open = gem_open(fd2, flink_name); dma_buf_fd2 = prime_handle_to_fd(fd2, handle_open); handle_import = prime_fd_to_handle(fd2, dma_buf_fd2); /* dma-buf selfimporting an flink bo should give the same handle */ igt_assert(handle_import == handle_open); close(fd1); close(fd2); close(dma_buf_fd1); close(dma_buf_fd2); } static void test_with_one_bo(void) { int fd1, fd2; uint32_t handle, handle_import1, handle_import2, handle_selfimport; int dma_buf_fd; fd1 = drm_open_any(); fd2 = drm_open_any(); handle = gem_create(fd1, BO_SIZE); dma_buf_fd = prime_handle_to_fd(fd1, handle); handle_import1 = prime_fd_to_handle(fd2, dma_buf_fd); check_bo(fd1, handle, fd2, handle_import1); /* reimport should give us the same handle so that userspace can check * whether it has that bo already somewhere. */ handle_import2 = prime_fd_to_handle(fd2, dma_buf_fd); igt_assert(handle_import1 == handle_import2); /* Same for re-importing on the exporting fd. */ handle_selfimport = prime_fd_to_handle(fd1, dma_buf_fd); igt_assert(handle == handle_selfimport); /* close dma_buf, check whether nothing disappears. */ close(dma_buf_fd); check_bo(fd1, handle, fd2, handle_import1); gem_close(fd1, handle); check_bo(fd2, handle_import1, fd2, handle_import1); /* re-import into old exporter */ dma_buf_fd = prime_handle_to_fd(fd2, handle_import1); /* but drop all references to the obj in between */ gem_close(fd2, handle_import1); handle = prime_fd_to_handle(fd1, dma_buf_fd); handle_import1 = prime_fd_to_handle(fd2, dma_buf_fd); check_bo(fd1, handle, fd2, handle_import1); /* Completely rip out exporting fd. */ close(fd1); check_bo(fd2, handle_import1, fd2, handle_import1); } static int get_object_count(void) { FILE *file; int ret, scanned; int device = drm_get_card(0); char *path; ret = asprintf(&path, "/sys/kernel/debug/dri/%d/i915_gem_objects", device); igt_assert(ret != -1); file = fopen(path, "r"); scanned = fscanf(file, "%i objects", &ret); igt_assert(scanned == 1); return ret; } static void *thread_fn_reimport_vs_close(void *p) { struct drm_gem_close close_bo; int *fds = p; int fd = fds[0]; int dma_buf_fd = fds[1]; uint32_t handle; while (!pls_die) { handle = prime_fd_to_handle(fd, dma_buf_fd); close_bo.handle = handle; ioctl(fd, DRM_IOCTL_GEM_CLOSE, &close_bo); } return (void *)0; } static void test_reimport_close_race(void) { pthread_t *threads; int r, i, num_threads; int fds[2]; int obj_count = get_object_count(); void *status; uint32_t handle; num_threads = sysconf(_SC_NPROCESSORS_ONLN); threads = calloc(num_threads, sizeof(pthread_t)); fds[0] = drm_open_any(); igt_assert(fds[0] >= 0); handle = gem_create(fds[0], BO_SIZE); fds[1] = prime_handle_to_fd(fds[0], handle); for (i = 0; i < num_threads; i++) { r = pthread_create(&threads[i], NULL, thread_fn_reimport_vs_close, (void *)(uintptr_t)fds); igt_assert(r == 0); } sleep(5); pls_die = 1; for (i = 0; i < num_threads; i++) { pthread_join(threads[i], &status); igt_assert(status == 0); } close(fds[0]); close(fds[1]); obj_count = get_object_count() - obj_count; printf("leaked %i objects\n", obj_count); igt_assert(obj_count == 0); } static void *thread_fn_export_vs_close(void *p) { struct drm_prime_handle prime_h2f; struct drm_gem_close close_bo; int fd = (uintptr_t)p; uint32_t handle; while (!pls_die) { /* We want to race gem close against prime export on handle one.*/ handle = gem_create(fd, 4096); if (handle != 1) gem_close(fd, handle); /* raw ioctl since we expect this to fail */ /* WTF: for gem_flink_race I've unconditionally used handle == 1 * here, but with prime it seems to help a _lot_ to use * something more random. */ prime_h2f.handle = 1; prime_h2f.flags = DRM_CLOEXEC; prime_h2f.fd = -1; ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime_h2f); close_bo.handle = 1; ioctl(fd, DRM_IOCTL_GEM_CLOSE, &close_bo); close(prime_h2f.fd); } return (void *)0; } static void test_export_close_race(void) { pthread_t *threads; int r, i, num_threads; int fd; int obj_count = get_object_count(); void *status; num_threads = sysconf(_SC_NPROCESSORS_ONLN); threads = calloc(num_threads, sizeof(pthread_t)); fd = drm_open_any(); igt_assert(fd >= 0); for (i = 0; i < num_threads; i++) { r = pthread_create(&threads[i], NULL, thread_fn_export_vs_close, (void *)(uintptr_t)fd); igt_assert(r == 0); } sleep(5); pls_die = 1; for (i = 0; i < num_threads; i++) { pthread_join(threads[i], &status); igt_assert(status == 0); } close(fd); obj_count = get_object_count() - obj_count; printf("leaked %i objects\n", obj_count); igt_assert(obj_count == 0); } int main(int argc, char **argv) { struct { const char *name; void (*fn)(void); } tests[] = { { "with_one_bo", test_with_one_bo }, { "with_one_bo_two_files", test_with_one_bo_two_files }, { "with_two_bos", test_with_two_bos }, { "with_fd_dup", test_with_fd_dup }, { "export-vs-gem_close-race", test_export_close_race }, { "reimport-vs-gem_close-race", test_reimport_close_race }, }; int i; igt_subtest_init(argc, argv); for (i = 0; i < ARRAY_SIZE(tests); i++) { igt_subtest(tests[i].name) tests[i].fn(); } igt_exit(); }