This test creates a udmabuf, and a dmabuf from the system dmabuf heap,
and uses a BPF program that prints dmabuf metadata with the new
dmabuf_iter to verify they can be found.

Signed-off-by: T.J. Mercier <tjmerc...@google.com>
---
 tools/testing/selftests/bpf/config            |   3 +
 .../selftests/bpf/prog_tests/dmabuf_iter.c    | 224 ++++++++++++++++++
 .../testing/selftests/bpf/progs/dmabuf_iter.c |  53 +++++
 3 files changed, 280 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
 create mode 100644 tools/testing/selftests/bpf/progs/dmabuf_iter.c

diff --git a/tools/testing/selftests/bpf/config 
b/tools/testing/selftests/bpf/config
index c378d5d07e02..2bdff2f3285f 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -22,6 +22,8 @@ CONFIG_CRYPTO_AES=y
 CONFIG_DEBUG_INFO=y
 CONFIG_DEBUG_INFO_BTF=y
 CONFIG_DEBUG_INFO_DWARF4=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
 CONFIG_DUMMY=y
 CONFIG_DYNAMIC_FTRACE=y
 CONFIG_FPROBE=y
@@ -106,6 +108,7 @@ CONFIG_SECURITY=y
 CONFIG_SECURITYFS=y
 CONFIG_SYN_COOKIES=y
 CONFIG_TEST_BPF=m
+CONFIG_UDMABUF=y
 CONFIG_USERFAULTFD=y
 CONFIG_VSOCKETS=y
 CONFIG_VXLAN=y
diff --git a/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c 
b/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
new file mode 100644
index 000000000000..35745f4ce0f8
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c
@@ -0,0 +1,224 @@
+// 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 memfd, udmabuf;
+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;
+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;
+
+       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"))
+               return 1;
+
+       if (!ASSERT_OK(fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK), "seal"))
+               return 1;
+
+       dev_udmabuf = open("/dev/udmabuf", O_RDONLY);
+       if (!ASSERT_OK_FD(dev_udmabuf, "open udmabuf"))
+               return 1;
+
+       create.memfd = memfd;
+       create.flags = UDMABUF_FLAGS_CLOEXEC;
+       create.offset = 0;
+       create.size = udmabuf_test_buffer_size;
+
+       udmabuf = ioctl(dev_udmabuf, UDMABUF_CREATE, &create);
+       close(dev_udmabuf);
+       if (!ASSERT_OK_FD(udmabuf, "udmabuf_create"))
+               return 1;
+
+       if (!ASSERT_OK(ioctl(udmabuf, DMA_BUF_SET_NAME_B, 
udmabuf_test_buffer_name), "name"))
+               return 1;
+
+       return 0;
+}
+
+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;
+
+       sysheap_dmabuf = data.fd;
+
+       if (!ASSERT_OK(ioctl(sysheap_dmabuf, DMA_BUF_SET_NAME_B, 
sysheap_test_buffer_name), "name"))
+               return 1;
+
+       return 0;
+}
+
+static int create_test_buffers(void)
+{
+       int ret;
+
+       ret = create_udmabuf();
+       if (ret)
+               return ret;
+
+       return create_sys_heap_dmabuf();
+}
+
+static void destroy_test_buffers(void)
+{
+       close(udmabuf);
+       close(memfd);
+       close(sysheap_dmabuf);
+}
+
+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_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));
+       ASSERT_OK_FD(iter_fd, "iter_create");
+
+       iter_file = fdopen(iter_fd, "r");
+       ASSERT_OK_PTR(iter_file, "fdopen");
+
+       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);
+}
+
+void test_dmabuf_iter(void)
+{
+       struct dmabuf_iter *skel = NULL;
+       char buf[256];
+       int iter_fd;
+
+       skel = dmabuf_iter__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "dmabuf_iter__open_and_load"))
+               return;
+
+       if (!ASSERT_OK(create_test_buffers(), "create_buffers"))
+               goto destroy;
+
+       if (!ASSERT_OK(dmabuf_iter__attach(skel), "skel_attach"))
+               goto destroy;
+
+       iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector));
+       if (!ASSERT_OK_FD(iter_fd, "iter_create"))
+               goto destroy;
+
+       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");
+
+       if (test__start_subtest("default_iter"))
+               subtest_dmabuf_iter_check_default_iter(skel);
+
+       close(iter_fd);
+
+destroy:
+       destroy_test_buffers();
+       dmabuf_iter__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/dmabuf_iter.c 
b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
new file mode 100644
index 000000000000..6e80a53c4670
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Google LLC */
+#include <vmlinux.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+
+/* From uapi/linux/dma-buf.h */
+#define DMA_BUF_NAME_LEN 32
+
+char _license[] SEC("license") = "GPL";
+
+/**
+ * Fields output by this iterator are delimited by newlines. Convert any
+ * newlines in user-provided printed strings to spaces.
+ */
+static void sanitize_string(char *src, size_t size)
+{
+       for (char *c = src; c && (size_t)(c-src) < size; ++c)
+               if (*c == '\n')
+                       *c = ' ';
+}
+
+SEC("iter/dmabuf")
+int dmabuf_collector(struct bpf_iter__dmabuf *ctx)
+{
+       const struct dma_buf *dmabuf = ctx->dmabuf;
+       struct seq_file *seq = ctx->meta->seq;
+       unsigned long inode = 0;
+       size_t size;
+       const char *pname, *exporter;
+       char name[DMA_BUF_NAME_LEN] = {'\0'};
+
+       if (!dmabuf)
+               return 0;
+
+       if (BPF_CORE_READ_INTO(&inode, dmabuf, file, f_inode, i_ino) ||
+           bpf_core_read(&size, sizeof(size), &dmabuf->size) ||
+           bpf_core_read(&pname, sizeof(pname), &dmabuf->name) ||
+           bpf_core_read(&exporter, sizeof(exporter), &dmabuf->exp_name))
+               return 1;
+
+       /* Buffers are not required to be named */
+       if (pname) {
+               if (bpf_probe_read_kernel(name, sizeof(name), pname))
+                       return 1;
+
+               /* Name strings can be provided by userspace */
+               sanitize_string(name, sizeof(name));
+       }
+
+       BPF_SEQ_PRINTF(seq, "%lu\n%llu\n%s\n%s\n", inode, size, name, exporter);
+       return 0;
+}
-- 
2.49.0.906.g1f30a19c02-goog

Reply via email to