/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright 2025 Google LLC. * Author: Suren Baghdasaryan * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * WrapFD API tests */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../kselftest_harness.h" /* ioctl wrappers */ static inline int wrapfd_wrap(int dev_fd, int fd, unsigned int prot) { struct wrapfd_wrap wrap = { .fd = fd, .prot = prot, }; return ioctl(dev_fd, WRAPFD_DEV_IOC_WRAP, &wrap); } static inline int wrapfd_get_state(int dev_fd, int fd, unsigned long *state) { struct wrapfd_get_state wrap_state = { .fd = fd, }; int ret; ret = ioctl(dev_fd, WRAPFD_DEV_IOC_GET_STATE, &wrap_state); if (!ret && state) *state = wrap_state.state; return ret; } static inline int wrapfd_get(int wrapfd) { return ioctl(wrapfd, WRAPFD_DEV_IOC_GET, NULL); } static inline int wrapfd_put(int wrapfd) { return ioctl(wrapfd, WRAPFD_DEV_IOC_PUT, NULL); } static inline int wrapfd_load(int wrapfd, int fd, unsigned long file_offs, unsigned long buf_offs, unsigned long len) { struct wrapfd_load load = { .fd = fd, .file_offs = file_offs, .buf_offs = buf_offs, .len = len, }; return ioctl(wrapfd, WRAPFD_DEV_IOC_LOAD, &load); } static inline int wrapfd_rewrap(int wrapfd, unsigned int prot) { struct wrapfd_rewrap rewrap = { .prot = prot, }; return ioctl(wrapfd, WRAPFD_DEV_IOC_REWRAP, &rewrap); } static inline int wrapfd_empty(int wrapfd) { return ioctl(wrapfd, WRAPFD_DEV_IOC_EMPTY, NULL); } static inline int wrapfd_allow_guests(int wrapfd) { return ioctl(wrapfd, WRAPFD_DEV_IOC_ALLOW_GUESTS, NULL); } static inline int wrapfd_prohibit_guests(int wrapfd) { return ioctl(wrapfd, WRAPFD_DEV_IOC_PROHIBIT_GUESTS, NULL); } /* test utility functions */ static int dmabuf_heap_alloc(int heap_fd, size_t len) { struct dma_heap_allocation_data data = { .len = len, .fd = 0, .fd_flags = O_RDWR | O_CLOEXEC, .heap_flags = 0, }; int ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data); return ret < 0 ? ret : data.fd; } static void generate_file_content(char *content, size_t size) { srand(time(NULL)); /* Generate only printable characters in the range of [32, 126] */ for (size_t i = 0; i < size; i++) content[i] = 32 + rand() % 95; } FIXTURE(wrapfd_tests) { size_t page_size; char *content; size_t size; int dev_fd; int fd; }; #define FILE_SZ_PAGES 100 FIXTURE_SETUP(wrapfd_tests) { ssize_t total_wr; FILE *ftmp; EXPECT_EQ(getuid(), 0) SKIP(return, "Skipping all tests as non-root"); self->page_size = (size_t)sysconf(_SC_PAGESIZE); self->size = self->page_size * FILE_SZ_PAGES; self->dev_fd = open("/dev/wrapfd", O_RDONLY); ASSERT_TRUE(self->dev_fd >= 0); /* Prepare random content buffer */ self->content = malloc(self->size); generate_file_content(self->content, self->size); /* Prepare temporary file with the same content */ ftmp = tmpfile(); ASSERT_NE(ftmp, NULL); self->fd = dup(fileno(ftmp)); fclose(ftmp); total_wr = 0; do { ssize_t wr = write(self->fd, self->content + total_wr, self->size - total_wr); ASSERT_TRUE(wr >= 0); total_wr += wr; } while (total_wr < self->size); } FIXTURE_TEARDOWN(wrapfd_tests) { close(self->fd); close(self->dev_fd); } static int cmp_content(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int wrapfd) { char *ptr; int ret; ptr = mmap(NULL, self->size, PROT_READ, MAP_SHARED, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); ret = memcmp(self->content, ptr, self->size); ASSERT_EQ(munmap(ptr, self->size), 0); return ret; } static void clear_content(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int wrapfd) { char *ptr; ptr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_SHARED, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); memset(ptr, 0, self->size); ASSERT_EQ(munmap(ptr, self->size), 0); } static void test_wrap(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd; /* Get state of a non-wrapped fd */ ASSERT_TRUE(wrapfd_get_state(self->dev_fd, fd, NULL) && errno == EINVAL); ASSERT_TRUE(wrapfd_get_state(self->dev_fd, self->dev_fd, NULL) && errno == EINVAL); /* Wrap and get state of a wrapped fd */ wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ); ASSERT_TRUE(wrapfd >= 0); ASSERT_EQ(wrapfd_get_state(self->dev_fd, wrapfd, NULL), 0); close(wrapfd); } static void test_load(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd; /* Load the file content first */ wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ | PROT_WRITE); ASSERT_TRUE(wrapfd >= 0); ASSERT_EQ(wrapfd_get(wrapfd), 0); clear_content(_metadata, self, wrapfd); ASSERT_NE(cmp_content(_metadata, self, wrapfd), 0); ASSERT_EQ(wrapfd_load(wrapfd, self->fd, 0, 0, self->size), 0); ASSERT_EQ(cmp_content(_metadata, self, wrapfd), 0); /* TODO: test more load offsets */ ASSERT_EQ(wrapfd_put(wrapfd), 0); close(wrapfd); } static void test_wrap_rdonly(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ); ASSERT_TRUE(wrapfd >= 0); /* Check content of the buffer */ ASSERT_EQ(cmp_content(_metadata, self, wrapfd), 0); /* Try mapping as writable */ ASSERT_EQ(mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, wrapfd, 0), MAP_FAILED); close(wrapfd); } static void test_wrap_rdwr(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd; char *ptr; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ | PROT_WRITE); ASSERT_TRUE(wrapfd >= 0); /* Check content of the buffer before modification */ ASSERT_EQ(cmp_content(_metadata, self, wrapfd), 0); /* Modify buffer content */ ptr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_SHARED, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); ptr[0]++; /* Check content of the buffer after modification */ ASSERT_NE(cmp_content(_metadata, self, wrapfd), 0); /* Restore buffer content */ ptr[0]--; /* Confirm the final content */ ASSERT_EQ(cmp_content(_metadata, self, wrapfd), 0); ASSERT_EQ(munmap(ptr, self->size), 0); close(wrapfd); } static void test_dup(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd, wrapfd2; char *ptr; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ); ASSERT_TRUE(wrapfd >= 0); wrapfd2 = dup(wrapfd); ASSERT_TRUE(wrapfd2 >= 0); /* Check content of the buffer using both fds */ ASSERT_EQ(cmp_content(_metadata, self, wrapfd), 0); ASSERT_EQ(cmp_content(_metadata, self, wrapfd2), 0); close(wrapfd2); close(wrapfd); } static void test_owner(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd; char *ptr; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ); ASSERT_TRUE(wrapfd >= 0); /* Try taking ownership of a mapped wrapfd */ ptr = mmap(NULL, self->size, PROT_READ, MAP_SHARED | MAP_POPULATE, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); ASSERT_TRUE(wrapfd_get(wrapfd) && errno == EINVAL); ASSERT_EQ(munmap(ptr, self->size), 0); /* Take ownership of an unmapped wrapfd */ ASSERT_EQ(wrapfd_get(wrapfd), 0); /* Map owned wrapfd */ ptr = mmap(NULL, self->size, PROT_READ, MAP_SHARED | MAP_POPULATE, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); /* Try releasing ownership while still mapped */ ASSERT_TRUE(wrapfd_put(wrapfd) && errno == EINVAL); ASSERT_EQ(munmap(ptr, self->size), 0); /* Release ownership of an unmapped wrapfd */ ASSERT_EQ(wrapfd_put(wrapfd), 0); close(wrapfd); } static void test_rewrap(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd, wrapfd2, wrapfd3; unsigned long state; char *ptr; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ); ASSERT_TRUE(wrapfd >= 0); ASSERT_TRUE(wrapfd_get_state(self->dev_fd, wrapfd, &state) == 0 && state == WRAPFD_CONTENT_RDONLY); /* Try rewrapping unowned buffer */ wrapfd2 = wrapfd_rewrap(wrapfd, PROT_WRITE); ASSERT_TRUE(wrapfd2 < 0 && errno == EBUSY); /* Take buffer ownership */ ASSERT_EQ(wrapfd_get(wrapfd), 0); /* Try rewrapping a mapped buffer */ ptr = mmap(NULL, self->size, PROT_READ, MAP_SHARED | MAP_POPULATE, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); wrapfd2 = wrapfd_rewrap(wrapfd, PROT_WRITE); ASSERT_TRUE(wrapfd2 < 0 && errno == EINVAL); ASSERT_EQ(munmap(ptr, self->size), 0); /* Rewrap the buffer to writable */ wrapfd2 = wrapfd_rewrap(wrapfd, PROT_WRITE); ASSERT_TRUE(wrapfd2 >= 0); /* Check the states of the new and original buffers */ ASSERT_TRUE(wrapfd_get_state(self->dev_fd, wrapfd2, &state) == 0 && state == WRAPFD_CONTENT_RDWR); ASSERT_TRUE(wrapfd_get_state(self->dev_fd, wrapfd, &state) == 0 && state == WRAPFD_CONTENT_EMPTY); /* Check rewrapped content */ ASSERT_EQ(cmp_content(_metadata, self, wrapfd2), 0); /* Take ownership of the rewrapped buffer */ ASSERT_EQ(wrapfd_get(wrapfd2), 0); /* Convert back to read-only */ wrapfd3 = wrapfd_rewrap(wrapfd2, PROT_READ); ASSERT_TRUE(wrapfd3 >= 0); ASSERT_TRUE(wrapfd_get_state(self->dev_fd, wrapfd3, &state) == 0 && state == WRAPFD_CONTENT_RDONLY); ASSERT_TRUE(wrapfd_get_state(self->dev_fd, wrapfd2, &state) == 0 && state == WRAPFD_CONTENT_EMPTY); /* Try mapping as writable */ ptr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, wrapfd3, 0); ASSERT_TRUE(ptr == MAP_FAILED && errno == EINVAL); /* Check rewrapped content */ ASSERT_EQ(cmp_content(_metadata, self, wrapfd3), 0); /* Try mapping the original empty wrap file */ ptr = mmap(NULL, self->size, PROT_READ, MAP_PRIVATE, wrapfd, 0); ASSERT_TRUE(ptr == MAP_FAILED && errno == ENOENT); /* Release ownership of the buffers */ ASSERT_EQ(wrapfd_put(wrapfd2), 0); ASSERT_EQ(wrapfd_put(wrapfd), 0); close(wrapfd3); close(wrapfd2); close(wrapfd); } static void test_empty(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { unsigned long state; int wrapfd; char *ptr; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ | PROT_WRITE); ASSERT_TRUE(wrapfd >= 0); /* Try emptying unowned buffer */ ASSERT_TRUE(wrapfd_empty(wrapfd) < 0 && errno == EBUSY); /* Take buffer ownership */ ASSERT_EQ(wrapfd_get(wrapfd), 0); /* Try emtying a mapped buffer */ ptr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); ASSERT_TRUE(wrapfd_empty(wrapfd) < 0 && errno == EINVAL); ASSERT_EQ(munmap(ptr, self->size), 0); /* Empty the buffer */ ASSERT_EQ(wrapfd_empty(wrapfd), 0); ASSERT_TRUE(wrapfd_get_state(self->dev_fd, wrapfd, &state) == 0 && state == WRAPFD_CONTENT_EMPTY); /* Try mapping the empty wrap file */ ptr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, wrapfd, 0); ASSERT_TRUE(ptr == MAP_FAILED && errno == ENOENT); /* Release buffer ownership */ ASSERT_EQ(wrapfd_put(wrapfd), 0); close(wrapfd); } static void test_guests(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { int wrapfd; char *ptr; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ | PROT_WRITE); ASSERT_TRUE(wrapfd >= 0); /* Try allowing guests for unowned buffer */ ASSERT_TRUE(wrapfd_allow_guests(wrapfd) < 0 && errno == EBUSY); /* Take buffer ownership */ ASSERT_EQ(wrapfd_get(wrapfd), 0); /* Try allowing guests for a mapped buffer */ ptr = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, wrapfd, 0); ASSERT_NE(ptr, MAP_FAILED); ASSERT_TRUE(wrapfd_allow_guests(wrapfd) < 0 && errno == EINVAL); ASSERT_EQ(munmap(ptr, self->size), 0); /* Allow guests */ ASSERT_EQ(wrapfd_allow_guests(wrapfd), 0); ASSERT_EQ(wrapfd_prohibit_guests(wrapfd), 0); ASSERT_EQ(wrapfd_allow_guests(wrapfd), 0); /* Release buffer ownership */ ASSERT_EQ(wrapfd_put(wrapfd), 0); close(wrapfd); } #define FDINFO_BUF_SIZE 4096 static void test_ioctl(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { const char *buf_name = "test_dmabuf"; char str[FDINFO_BUF_SIZE]; char fdinfo_name[64]; int fdinfo_fd; int wrapfd; wrapfd = wrapfd_wrap(self->dev_fd, fd, PROT_READ | PROT_WRITE); ASSERT_TRUE(wrapfd >= 0); ASSERT_EQ(ioctl(wrapfd, DMA_BUF_SET_NAME, buf_name), 0); /* Verify the dmabuf name is changed */ sprintf(fdinfo_name, "/proc/%d/fdinfo/%d", getpid(), fd); fdinfo_fd = open(fdinfo_name, O_RDONLY); ASSERT_TRUE(fdinfo_fd >= 0); ASSERT_TRUE(read(fdinfo_fd, str, FDINFO_BUF_SIZE) > 0); ASSERT_NE(strstr(str, buf_name), NULL); close(fdinfo_fd); /* Verify the dmabuf name is changed in wrapfd fdinfo too */ sprintf(fdinfo_name, "/proc/%d/fdinfo/%d", getpid(), wrapfd); fdinfo_fd = open(fdinfo_name, O_RDONLY); ASSERT_TRUE(fdinfo_fd >= 0); ASSERT_TRUE(read(fdinfo_fd, str, FDINFO_BUF_SIZE) > 0); ASSERT_NE(strstr(str, buf_name), NULL); close(fdinfo_fd); close(wrapfd); } static void run_tests(struct __test_metadata *_metadata, FIXTURE_DATA(wrapfd_tests) *self, int fd) { test_wrap(_metadata, self, fd); test_load(_metadata, self, fd); test_wrap_rdonly(_metadata, self, fd); test_wrap_rdwr(_metadata, self, fd); test_dup(_metadata, self, fd); test_owner(_metadata, self, fd); test_rewrap(_metadata, self, fd); test_empty(_metadata, self, fd); test_guests(_metadata, self, fd); test_ioctl(_metadata, self, fd); } TEST_F(wrapfd_tests, wrapfd_test_dmabuf_system_heap) { int dmabuf_fd; int heap_fd; heap_fd = open("/dev/dma_heap/system", O_RDONLY); ASSERT_TRUE(heap_fd >= 0); dmabuf_fd = dmabuf_heap_alloc(heap_fd, self->size); ASSERT_TRUE(dmabuf_fd >= 0); close(heap_fd); run_tests(_metadata, self, dmabuf_fd); close(dmabuf_fd); } TEST_F(wrapfd_tests, wrapfd_test_dmabuf_cma_heap) { int dmabuf_fd; int heap_fd; heap_fd = open("/dev/dma_heap/default_cma_region", O_RDONLY); if (heap_fd < 0 && errno == ENOENT) SKIP(return, "Skipping CMA tests as the CMA heap is missing"); ASSERT_TRUE(heap_fd >= 0); dmabuf_fd = dmabuf_heap_alloc(heap_fd, self->size); ASSERT_TRUE(dmabuf_fd >= 0); close(heap_fd); run_tests(_metadata, self, dmabuf_fd); close(dmabuf_fd); } TEST_HARNESS_MAIN