// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Google */ #include #include #include #include "dmabuf_iter.skel.h" #include #include #include #include #include #include #include #include #include #include #include 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); }