| // SPDX-License-Identifier: GPL-2.0 |
| #define _GNU_SOURCE |
| #define __EXPORTED_HEADERS__ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <malloc.h> |
| #include <stdbool.h> |
| |
| #include <sys/ioctl.h> |
| #include <sys/syscall.h> |
| #include <sys/mman.h> |
| #include <linux/memfd.h> |
| #include <linux/udmabuf.h> |
| #include "../../kselftest.h" |
| |
| #define TEST_PREFIX "drivers/dma-buf/udmabuf" |
| #define NUM_PAGES 4 |
| #define NUM_ENTRIES 4 |
| #define MEMFD_SIZE 1024 /* in pages */ |
| |
| static unsigned int page_size; |
| |
| static int create_memfd_with_seals(off64_t size, bool hpage) |
| { |
| int memfd, ret; |
| unsigned int flags = MFD_ALLOW_SEALING; |
| |
| if (hpage) |
| flags |= MFD_HUGETLB; |
| |
| memfd = memfd_create("udmabuf-test", flags); |
| if (memfd < 0) { |
| ksft_print_msg("%s: [skip,no-memfd]\n", TEST_PREFIX); |
| exit(KSFT_SKIP); |
| } |
| |
| ret = fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK); |
| if (ret < 0) { |
| ksft_print_msg("%s: [skip,fcntl-add-seals]\n", TEST_PREFIX); |
| exit(KSFT_SKIP); |
| } |
| |
| ret = ftruncate(memfd, size); |
| if (ret == -1) { |
| ksft_print_msg("%s: [FAIL,memfd-truncate]\n", TEST_PREFIX); |
| exit(KSFT_FAIL); |
| } |
| |
| return memfd; |
| } |
| |
| static int create_udmabuf_list(int devfd, int memfd, off64_t memfd_size) |
| { |
| struct udmabuf_create_list *list; |
| int ubuf_fd, i; |
| |
| list = malloc(sizeof(struct udmabuf_create_list) + |
| sizeof(struct udmabuf_create_item) * NUM_ENTRIES); |
| if (!list) { |
| ksft_print_msg("%s: [FAIL, udmabuf-malloc]\n", TEST_PREFIX); |
| exit(KSFT_FAIL); |
| } |
| |
| for (i = 0; i < NUM_ENTRIES; i++) { |
| list->list[i].memfd = memfd; |
| list->list[i].offset = i * (memfd_size / NUM_ENTRIES); |
| list->list[i].size = getpagesize() * NUM_PAGES; |
| } |
| |
| list->count = NUM_ENTRIES; |
| list->flags = UDMABUF_FLAGS_CLOEXEC; |
| ubuf_fd = ioctl(devfd, UDMABUF_CREATE_LIST, list); |
| free(list); |
| if (ubuf_fd < 0) { |
| ksft_print_msg("%s: [FAIL, udmabuf-create]\n", TEST_PREFIX); |
| exit(KSFT_FAIL); |
| } |
| |
| return ubuf_fd; |
| } |
| |
| static void write_to_memfd(void *addr, off64_t size, char chr) |
| { |
| int i; |
| |
| for (i = 0; i < size / page_size; i++) { |
| *((char *)addr + (i * page_size)) = chr; |
| } |
| } |
| |
| static void *mmap_fd(int fd, off64_t size) |
| { |
| void *addr; |
| |
| addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); |
| if (addr == MAP_FAILED) { |
| ksft_print_msg("%s: ubuf_fd mmap fail\n", TEST_PREFIX); |
| exit(KSFT_FAIL); |
| } |
| |
| return addr; |
| } |
| |
| static int compare_chunks(void *addr1, void *addr2, off64_t memfd_size) |
| { |
| off64_t off; |
| int i = 0, j, k = 0, ret = 0; |
| char char1, char2; |
| |
| while (i < NUM_ENTRIES) { |
| off = i * (memfd_size / NUM_ENTRIES); |
| for (j = 0; j < NUM_PAGES; j++, k++) { |
| char1 = *((char *)addr1 + off + (j * getpagesize())); |
| char2 = *((char *)addr2 + (k * getpagesize())); |
| if (char1 != char2) { |
| ret = -1; |
| goto err; |
| } |
| } |
| i++; |
| } |
| err: |
| munmap(addr1, memfd_size); |
| munmap(addr2, NUM_ENTRIES * NUM_PAGES * getpagesize()); |
| return ret; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct udmabuf_create create; |
| int devfd, memfd, buf, ret; |
| off64_t size; |
| void *addr1, *addr2; |
| |
| ksft_print_header(); |
| ksft_set_plan(6); |
| |
| devfd = open("/dev/udmabuf", O_RDWR); |
| if (devfd < 0) { |
| ksft_print_msg( |
| "%s: [skip,no-udmabuf: Unable to access DMA buffer device file]\n", |
| TEST_PREFIX); |
| exit(KSFT_SKIP); |
| } |
| |
| memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING); |
| if (memfd < 0) { |
| ksft_print_msg("%s: [skip,no-memfd]\n", TEST_PREFIX); |
| exit(KSFT_SKIP); |
| } |
| |
| ret = fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK); |
| if (ret < 0) { |
| ksft_print_msg("%s: [skip,fcntl-add-seals]\n", TEST_PREFIX); |
| exit(KSFT_SKIP); |
| } |
| |
| size = getpagesize() * NUM_PAGES; |
| ret = ftruncate(memfd, size); |
| if (ret == -1) { |
| ksft_print_msg("%s: [FAIL,memfd-truncate]\n", TEST_PREFIX); |
| exit(KSFT_FAIL); |
| } |
| |
| memset(&create, 0, sizeof(create)); |
| |
| /* should fail (offset not page aligned) */ |
| create.memfd = memfd; |
| create.offset = getpagesize()/2; |
| create.size = getpagesize(); |
| buf = ioctl(devfd, UDMABUF_CREATE, &create); |
| if (buf >= 0) |
| ksft_test_result_fail("%s: [FAIL,test-1]\n", TEST_PREFIX); |
| else |
| ksft_test_result_pass("%s: [PASS,test-1]\n", TEST_PREFIX); |
| |
| /* should fail (size not multiple of page) */ |
| create.memfd = memfd; |
| create.offset = 0; |
| create.size = getpagesize()/2; |
| buf = ioctl(devfd, UDMABUF_CREATE, &create); |
| if (buf >= 0) |
| ksft_test_result_fail("%s: [FAIL,test-2]\n", TEST_PREFIX); |
| else |
| ksft_test_result_pass("%s: [PASS,test-2]\n", TEST_PREFIX); |
| |
| /* should fail (not memfd) */ |
| create.memfd = 0; /* stdin */ |
| create.offset = 0; |
| create.size = size; |
| buf = ioctl(devfd, UDMABUF_CREATE, &create); |
| if (buf >= 0) |
| ksft_test_result_fail("%s: [FAIL,test-3]\n", TEST_PREFIX); |
| else |
| ksft_test_result_pass("%s: [PASS,test-3]\n", TEST_PREFIX); |
| |
| /* should work */ |
| page_size = getpagesize(); |
| addr1 = mmap_fd(memfd, size); |
| write_to_memfd(addr1, size, 'a'); |
| create.memfd = memfd; |
| create.offset = 0; |
| create.size = size; |
| buf = ioctl(devfd, UDMABUF_CREATE, &create); |
| if (buf < 0) |
| ksft_test_result_fail("%s: [FAIL,test-4]\n", TEST_PREFIX); |
| else |
| ksft_test_result_pass("%s: [PASS,test-4]\n", TEST_PREFIX); |
| |
| munmap(addr1, size); |
| close(buf); |
| close(memfd); |
| |
| /* should work (migration of 4k size pages)*/ |
| size = MEMFD_SIZE * page_size; |
| memfd = create_memfd_with_seals(size, false); |
| addr1 = mmap_fd(memfd, size); |
| write_to_memfd(addr1, size, 'a'); |
| buf = create_udmabuf_list(devfd, memfd, size); |
| addr2 = mmap_fd(buf, NUM_PAGES * NUM_ENTRIES * getpagesize()); |
| write_to_memfd(addr1, size, 'b'); |
| ret = compare_chunks(addr1, addr2, size); |
| if (ret < 0) |
| ksft_test_result_fail("%s: [FAIL,test-5]\n", TEST_PREFIX); |
| else |
| ksft_test_result_pass("%s: [PASS,test-5]\n", TEST_PREFIX); |
| |
| close(buf); |
| close(memfd); |
| |
| /* should work (migration of 2MB size huge pages)*/ |
| page_size = getpagesize() * 512; /* 2 MB */ |
| size = MEMFD_SIZE * page_size; |
| memfd = create_memfd_with_seals(size, true); |
| addr1 = mmap_fd(memfd, size); |
| write_to_memfd(addr1, size, 'a'); |
| buf = create_udmabuf_list(devfd, memfd, size); |
| addr2 = mmap_fd(buf, NUM_PAGES * NUM_ENTRIES * getpagesize()); |
| write_to_memfd(addr1, size, 'b'); |
| ret = compare_chunks(addr1, addr2, size); |
| if (ret < 0) |
| ksft_test_result_fail("%s: [FAIL,test-6]\n", TEST_PREFIX); |
| else |
| ksft_test_result_pass("%s: [PASS,test-6]\n", TEST_PREFIX); |
| |
| close(buf); |
| close(memfd); |
| close(devfd); |
| |
| ksft_print_msg("%s: ok\n", TEST_PREFIX); |
| ksft_print_cnts(); |
| |
| return 0; |
| } |