| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2025 Google */ |
| |
| #include <test_progs.h> |
| #include <bpf/libbpf.h> |
| #include <bpf/btf.h> |
| #include "dmabuf_iter.skel.h" |
| |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| #include <linux/dma-buf.h> |
| #include <linux/dma-heap.h> |
| #include <linux/udmabuf.h> |
| |
| static int udmabuf = -1; |
| static const char udmabuf_test_buffer_name[DMA_BUF_NAME_LEN] = "udmabuf_test_buffer_for_iter"; |
| static size_t udmabuf_test_buffer_size; |
| static int sysheap_dmabuf = -1; |
| static const char sysheap_test_buffer_name[DMA_BUF_NAME_LEN] = "sysheap_test_buffer_for_iter"; |
| static size_t sysheap_test_buffer_size; |
| |
| static int create_udmabuf(void) |
| { |
| struct udmabuf_create create; |
| int dev_udmabuf, memfd, local_udmabuf; |
| |
| udmabuf_test_buffer_size = 10 * getpagesize(); |
| |
| if (!ASSERT_LE(sizeof(udmabuf_test_buffer_name), DMA_BUF_NAME_LEN, "NAMETOOLONG")) |
| return -1; |
| |
| memfd = memfd_create("memfd_test", MFD_ALLOW_SEALING); |
| if (!ASSERT_OK_FD(memfd, "memfd_create")) |
| return -1; |
| |
| if (!ASSERT_OK(ftruncate(memfd, udmabuf_test_buffer_size), "ftruncate")) |
| goto close_memfd; |
| |
| if (!ASSERT_OK(fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK), "seal")) |
| goto close_memfd; |
| |
| dev_udmabuf = open("/dev/udmabuf", O_RDONLY); |
| if (!ASSERT_OK_FD(dev_udmabuf, "open udmabuf")) |
| goto close_memfd; |
| |
| memset(&create, 0, sizeof(create)); |
| create.memfd = memfd; |
| create.flags = UDMABUF_FLAGS_CLOEXEC; |
| create.offset = 0; |
| create.size = udmabuf_test_buffer_size; |
| |
| local_udmabuf = ioctl(dev_udmabuf, UDMABUF_CREATE, &create); |
| close(dev_udmabuf); |
| if (!ASSERT_OK_FD(local_udmabuf, "udmabuf_create")) |
| goto close_memfd; |
| |
| if (!ASSERT_OK(ioctl(local_udmabuf, DMA_BUF_SET_NAME_B, udmabuf_test_buffer_name), "name")) |
| goto close_udmabuf; |
| |
| return local_udmabuf; |
| |
| close_udmabuf: |
| close(local_udmabuf); |
| close_memfd: |
| close(memfd); |
| return -1; |
| } |
| |
| static int create_sys_heap_dmabuf(void) |
| { |
| sysheap_test_buffer_size = 20 * getpagesize(); |
| |
| struct dma_heap_allocation_data data = { |
| .len = sysheap_test_buffer_size, |
| .fd = 0, |
| .fd_flags = O_RDWR | O_CLOEXEC, |
| .heap_flags = 0, |
| }; |
| int heap_fd, ret; |
| |
| if (!ASSERT_LE(sizeof(sysheap_test_buffer_name), DMA_BUF_NAME_LEN, "NAMETOOLONG")) |
| return -1; |
| |
| heap_fd = open("/dev/dma_heap/system", O_RDONLY); |
| if (!ASSERT_OK_FD(heap_fd, "open dma heap")) |
| return -1; |
| |
| ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data); |
| close(heap_fd); |
| if (!ASSERT_OK(ret, "syheap alloc")) |
| return -1; |
| |
| if (!ASSERT_OK(ioctl(data.fd, DMA_BUF_SET_NAME_B, sysheap_test_buffer_name), "name")) |
| goto close_sysheap_dmabuf; |
| |
| return data.fd; |
| |
| close_sysheap_dmabuf: |
| close(data.fd); |
| return -1; |
| } |
| |
| static int create_test_buffers(void) |
| { |
| udmabuf = create_udmabuf(); |
| sysheap_dmabuf = create_sys_heap_dmabuf(); |
| |
| if (udmabuf < 0 || sysheap_dmabuf < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void destroy_test_buffers(void) |
| { |
| close(udmabuf); |
| udmabuf = -1; |
| |
| close(sysheap_dmabuf); |
| sysheap_dmabuf = -1; |
| } |
| |
| enum Fields { INODE, SIZE, NAME, EXPORTER, FIELD_COUNT }; |
| struct DmabufInfo { |
| unsigned long inode; |
| unsigned long size; |
| char name[DMA_BUF_NAME_LEN]; |
| char exporter[32]; |
| }; |
| |
| static bool check_dmabuf_info(const struct DmabufInfo *bufinfo, |
| unsigned long size, |
| const char *name, const char *exporter) |
| { |
| return size == bufinfo->size && |
| !strcmp(name, bufinfo->name) && |
| !strcmp(exporter, bufinfo->exporter); |
| } |
| |
| static void subtest_dmabuf_iter_check_no_infinite_reads(struct dmabuf_iter *skel) |
| { |
| int iter_fd; |
| char buf[256]; |
| |
| iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector)); |
| if (!ASSERT_OK_FD(iter_fd, "iter_create")) |
| return; |
| |
| while (read(iter_fd, buf, sizeof(buf)) > 0) |
| ; /* Read out all contents */ |
| |
| /* Next reads should return 0 */ |
| ASSERT_EQ(read(iter_fd, buf, sizeof(buf)), 0, "read"); |
| |
| close(iter_fd); |
| } |
| |
| static void subtest_dmabuf_iter_check_default_iter(struct dmabuf_iter *skel) |
| { |
| bool found_test_sysheap_dmabuf = false; |
| bool found_test_udmabuf = false; |
| struct DmabufInfo bufinfo; |
| size_t linesize = 0; |
| char *line = NULL; |
| FILE *iter_file; |
| int iter_fd, f = INODE; |
| |
| iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector)); |
| if (!ASSERT_OK_FD(iter_fd, "iter_create")) |
| return; |
| |
| iter_file = fdopen(iter_fd, "r"); |
| if (!ASSERT_OK_PTR(iter_file, "fdopen")) |
| goto close_iter_fd; |
| |
| while (getline(&line, &linesize, iter_file) != -1) { |
| if (f % FIELD_COUNT == INODE) { |
| ASSERT_EQ(sscanf(line, "%ld", &bufinfo.inode), 1, |
| "read inode"); |
| } else if (f % FIELD_COUNT == SIZE) { |
| ASSERT_EQ(sscanf(line, "%ld", &bufinfo.size), 1, |
| "read size"); |
| } else if (f % FIELD_COUNT == NAME) { |
| ASSERT_EQ(sscanf(line, "%s", bufinfo.name), 1, |
| "read name"); |
| } else if (f % FIELD_COUNT == EXPORTER) { |
| ASSERT_EQ(sscanf(line, "%31s", bufinfo.exporter), 1, |
| "read exporter"); |
| |
| if (check_dmabuf_info(&bufinfo, |
| sysheap_test_buffer_size, |
| sysheap_test_buffer_name, |
| "system")) |
| found_test_sysheap_dmabuf = true; |
| else if (check_dmabuf_info(&bufinfo, |
| udmabuf_test_buffer_size, |
| udmabuf_test_buffer_name, |
| "udmabuf")) |
| found_test_udmabuf = true; |
| } |
| ++f; |
| } |
| |
| ASSERT_EQ(f % FIELD_COUNT, INODE, "number of fields"); |
| |
| ASSERT_TRUE(found_test_sysheap_dmabuf, "found_test_sysheap_dmabuf"); |
| ASSERT_TRUE(found_test_udmabuf, "found_test_udmabuf"); |
| |
| free(line); |
| fclose(iter_file); |
| close_iter_fd: |
| close(iter_fd); |
| } |
| |
| static void subtest_dmabuf_iter_check_open_coded(struct dmabuf_iter *skel, int map_fd) |
| { |
| LIBBPF_OPTS(bpf_test_run_opts, topts); |
| char key[DMA_BUF_NAME_LEN]; |
| int err, fd; |
| bool found; |
| |
| /* No need to attach it, just run it directly */ |
| fd = bpf_program__fd(skel->progs.iter_dmabuf_for_each); |
| |
| err = bpf_prog_test_run_opts(fd, &topts); |
| if (!ASSERT_OK(err, "test_run_opts err")) |
| return; |
| if (!ASSERT_OK(topts.retval, "test_run_opts retval")) |
| return; |
| |
| if (!ASSERT_OK(bpf_map_get_next_key(map_fd, NULL, key), "get next key")) |
| return; |
| |
| do { |
| ASSERT_OK(bpf_map_lookup_elem(map_fd, key, &found), "lookup"); |
| ASSERT_TRUE(found, "found test buffer"); |
| } while (bpf_map_get_next_key(map_fd, key, key)); |
| } |
| |
| void test_dmabuf_iter(void) |
| { |
| struct dmabuf_iter *skel = NULL; |
| int map_fd; |
| const bool f = false; |
| |
| skel = dmabuf_iter__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "dmabuf_iter__open_and_load")) |
| return; |
| |
| map_fd = bpf_map__fd(skel->maps.testbuf_hash); |
| if (!ASSERT_OK_FD(map_fd, "map_fd")) |
| goto destroy_skel; |
| |
| if (!ASSERT_OK(bpf_map_update_elem(map_fd, udmabuf_test_buffer_name, &f, BPF_ANY), |
| "insert udmabuf")) |
| goto destroy_skel; |
| if (!ASSERT_OK(bpf_map_update_elem(map_fd, sysheap_test_buffer_name, &f, BPF_ANY), |
| "insert sysheap buffer")) |
| goto destroy_skel; |
| |
| if (!ASSERT_OK(create_test_buffers(), "create_test_buffers")) |
| goto destroy; |
| |
| if (!ASSERT_OK(dmabuf_iter__attach(skel), "skel_attach")) |
| goto destroy; |
| |
| if (test__start_subtest("no_infinite_reads")) |
| subtest_dmabuf_iter_check_no_infinite_reads(skel); |
| if (test__start_subtest("default_iter")) |
| subtest_dmabuf_iter_check_default_iter(skel); |
| if (test__start_subtest("open_coded")) |
| subtest_dmabuf_iter_check_open_coded(skel, map_fd); |
| |
| destroy: |
| destroy_test_buffers(); |
| destroy_skel: |
| dmabuf_iter__destroy(skel); |
| } |