1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2025 Google */
3
4 #include <test_progs.h>
5 #include <bpf/libbpf.h>
6 #include <bpf/btf.h>
7 #include "dmabuf_iter.skel.h"
8
9 #include <fcntl.h>
10 #include <stdbool.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/ioctl.h>
15 #include <sys/mman.h>
16 #include <unistd.h>
17
18 #include <linux/dma-buf.h>
19 #include <linux/dma-heap.h>
20 #include <linux/udmabuf.h>
21
22 static int udmabuf = -1;
23 static const char udmabuf_test_buffer_name[DMA_BUF_NAME_LEN] = "udmabuf_test_buffer_for_iter";
24 static size_t udmabuf_test_buffer_size;
25 static int sysheap_dmabuf = -1;
26 static const char sysheap_test_buffer_name[DMA_BUF_NAME_LEN] = "sysheap_test_buffer_for_iter";
27 static size_t sysheap_test_buffer_size;
28
create_udmabuf(void)29 static int create_udmabuf(void)
30 {
31 struct udmabuf_create create;
32 int dev_udmabuf, memfd, local_udmabuf;
33
34 udmabuf_test_buffer_size = 10 * getpagesize();
35
36 if (!ASSERT_LE(sizeof(udmabuf_test_buffer_name), DMA_BUF_NAME_LEN, "NAMETOOLONG"))
37 return -1;
38
39 memfd = memfd_create("memfd_test", MFD_ALLOW_SEALING);
40 if (!ASSERT_OK_FD(memfd, "memfd_create"))
41 return -1;
42
43 if (!ASSERT_OK(ftruncate(memfd, udmabuf_test_buffer_size), "ftruncate"))
44 goto close_memfd;
45
46 if (!ASSERT_OK(fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK), "seal"))
47 goto close_memfd;
48
49 dev_udmabuf = open("/dev/udmabuf", O_RDONLY);
50 if (!ASSERT_OK_FD(dev_udmabuf, "open udmabuf"))
51 goto close_memfd;
52
53 memset(&create, 0, sizeof(create));
54 create.memfd = memfd;
55 create.flags = UDMABUF_FLAGS_CLOEXEC;
56 create.offset = 0;
57 create.size = udmabuf_test_buffer_size;
58
59 local_udmabuf = ioctl(dev_udmabuf, UDMABUF_CREATE, &create);
60 close(dev_udmabuf);
61 if (!ASSERT_OK_FD(local_udmabuf, "udmabuf_create"))
62 goto close_memfd;
63
64 if (!ASSERT_OK(ioctl(local_udmabuf, DMA_BUF_SET_NAME_B, udmabuf_test_buffer_name), "name"))
65 goto close_udmabuf;
66
67 return local_udmabuf;
68
69 close_udmabuf:
70 close(local_udmabuf);
71 close_memfd:
72 close(memfd);
73 return -1;
74 }
75
create_sys_heap_dmabuf(size_t bytes)76 static int create_sys_heap_dmabuf(size_t bytes)
77 {
78 struct dma_heap_allocation_data data = {
79 .len = bytes,
80 .fd = 0,
81 .fd_flags = O_RDWR | O_CLOEXEC,
82 .heap_flags = 0,
83 };
84 int heap_fd, ret;
85
86 if (!ASSERT_LE(sizeof(sysheap_test_buffer_name), DMA_BUF_NAME_LEN, "NAMETOOLONG"))
87 return -1;
88
89 heap_fd = open("/dev/dma_heap/system", O_RDONLY);
90 if (!ASSERT_OK_FD(heap_fd, "open dma heap"))
91 return -1;
92
93 ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data);
94 close(heap_fd);
95 if (!ASSERT_OK(ret, "syheap alloc"))
96 return -1;
97
98 if (!ASSERT_OK(ioctl(data.fd, DMA_BUF_SET_NAME_B, sysheap_test_buffer_name), "name"))
99 goto close_sysheap_dmabuf;
100
101 return data.fd;
102
103 close_sysheap_dmabuf:
104 close(data.fd);
105 return -1;
106 }
107
create_test_buffers(void)108 static int create_test_buffers(void)
109 {
110 udmabuf = create_udmabuf();
111
112 sysheap_test_buffer_size = 20 * getpagesize();
113 sysheap_dmabuf = create_sys_heap_dmabuf(sysheap_test_buffer_size);
114
115 if (udmabuf < 0 || sysheap_dmabuf < 0)
116 return -1;
117
118 return 0;
119 }
120
destroy_test_buffers(void)121 static void destroy_test_buffers(void)
122 {
123 close(udmabuf);
124 udmabuf = -1;
125
126 close(sysheap_dmabuf);
127 sysheap_dmabuf = -1;
128 }
129
130 enum Fields { INODE, SIZE, NAME, EXPORTER, FIELD_COUNT };
131 struct DmabufInfo {
132 unsigned long inode;
133 unsigned long size;
134 char name[DMA_BUF_NAME_LEN];
135 char exporter[32];
136 };
137
check_dmabuf_info(const struct DmabufInfo * bufinfo,unsigned long size,const char * name,const char * exporter)138 static bool check_dmabuf_info(const struct DmabufInfo *bufinfo,
139 unsigned long size,
140 const char *name, const char *exporter)
141 {
142 return size == bufinfo->size &&
143 !strcmp(name, bufinfo->name) &&
144 !strcmp(exporter, bufinfo->exporter);
145 }
146
subtest_dmabuf_iter_check_no_infinite_reads(struct dmabuf_iter * skel)147 static void subtest_dmabuf_iter_check_no_infinite_reads(struct dmabuf_iter *skel)
148 {
149 int iter_fd;
150 char buf[256];
151
152 iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector));
153 if (!ASSERT_OK_FD(iter_fd, "iter_create"))
154 return;
155
156 while (read(iter_fd, buf, sizeof(buf)) > 0)
157 ; /* Read out all contents */
158
159 /* Next reads should return 0 */
160 ASSERT_EQ(read(iter_fd, buf, sizeof(buf)), 0, "read");
161
162 close(iter_fd);
163 }
164
subtest_dmabuf_iter_check_default_iter(struct dmabuf_iter * skel)165 static void subtest_dmabuf_iter_check_default_iter(struct dmabuf_iter *skel)
166 {
167 bool found_test_sysheap_dmabuf = false;
168 bool found_test_udmabuf = false;
169 struct DmabufInfo bufinfo;
170 size_t linesize = 0;
171 char *line = NULL;
172 FILE *iter_file;
173 int iter_fd, f = INODE;
174
175 iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector));
176 if (!ASSERT_OK_FD(iter_fd, "iter_create"))
177 return;
178
179 iter_file = fdopen(iter_fd, "r");
180 if (!ASSERT_OK_PTR(iter_file, "fdopen"))
181 goto close_iter_fd;
182
183 while (getline(&line, &linesize, iter_file) != -1) {
184 if (f % FIELD_COUNT == INODE) {
185 ASSERT_EQ(sscanf(line, "%ld", &bufinfo.inode), 1,
186 "read inode");
187 } else if (f % FIELD_COUNT == SIZE) {
188 ASSERT_EQ(sscanf(line, "%ld", &bufinfo.size), 1,
189 "read size");
190 } else if (f % FIELD_COUNT == NAME) {
191 ASSERT_EQ(sscanf(line, "%s", bufinfo.name), 1,
192 "read name");
193 } else if (f % FIELD_COUNT == EXPORTER) {
194 ASSERT_EQ(sscanf(line, "%31s", bufinfo.exporter), 1,
195 "read exporter");
196
197 if (check_dmabuf_info(&bufinfo,
198 sysheap_test_buffer_size,
199 sysheap_test_buffer_name,
200 "system"))
201 found_test_sysheap_dmabuf = true;
202 else if (check_dmabuf_info(&bufinfo,
203 udmabuf_test_buffer_size,
204 udmabuf_test_buffer_name,
205 "udmabuf"))
206 found_test_udmabuf = true;
207 }
208 ++f;
209 }
210
211 ASSERT_EQ(f % FIELD_COUNT, INODE, "number of fields");
212
213 ASSERT_TRUE(found_test_sysheap_dmabuf, "found_test_sysheap_dmabuf");
214 ASSERT_TRUE(found_test_udmabuf, "found_test_udmabuf");
215
216 free(line);
217 fclose(iter_file);
218 close_iter_fd:
219 close(iter_fd);
220 }
221
subtest_dmabuf_iter_check_lots_of_buffers(struct dmabuf_iter * skel)222 static void subtest_dmabuf_iter_check_lots_of_buffers(struct dmabuf_iter *skel)
223 {
224 int iter_fd;
225 char buf[1024];
226 size_t total_bytes_read = 0;
227 ssize_t bytes_read;
228
229 iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector));
230 if (!ASSERT_OK_FD(iter_fd, "iter_create"))
231 return;
232
233 while ((bytes_read = read(iter_fd, buf, sizeof(buf))) > 0)
234 total_bytes_read += bytes_read;
235
236 ASSERT_GT(total_bytes_read, getpagesize(), "total_bytes_read");
237
238 close(iter_fd);
239 }
240
241
subtest_dmabuf_iter_check_open_coded(struct dmabuf_iter * skel,int map_fd)242 static void subtest_dmabuf_iter_check_open_coded(struct dmabuf_iter *skel, int map_fd)
243 {
244 LIBBPF_OPTS(bpf_test_run_opts, topts);
245 char key[DMA_BUF_NAME_LEN];
246 int err, fd;
247 bool found;
248
249 /* No need to attach it, just run it directly */
250 fd = bpf_program__fd(skel->progs.iter_dmabuf_for_each);
251
252 err = bpf_prog_test_run_opts(fd, &topts);
253 if (!ASSERT_OK(err, "test_run_opts err"))
254 return;
255 if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
256 return;
257
258 if (!ASSERT_OK(bpf_map_get_next_key(map_fd, NULL, key), "get next key"))
259 return;
260
261 do {
262 ASSERT_OK(bpf_map_lookup_elem(map_fd, key, &found), "lookup");
263 ASSERT_TRUE(found, "found test buffer");
264 } while (bpf_map_get_next_key(map_fd, key, key));
265 }
266
test_dmabuf_iter(void)267 void test_dmabuf_iter(void)
268 {
269 struct dmabuf_iter *skel = NULL;
270 int map_fd;
271 const bool f = false;
272
273 skel = dmabuf_iter__open_and_load();
274 if (!ASSERT_OK_PTR(skel, "dmabuf_iter__open_and_load"))
275 return;
276
277 map_fd = bpf_map__fd(skel->maps.testbuf_hash);
278 if (!ASSERT_OK_FD(map_fd, "map_fd"))
279 goto destroy_skel;
280
281 if (!ASSERT_OK(bpf_map_update_elem(map_fd, udmabuf_test_buffer_name, &f, BPF_ANY),
282 "insert udmabuf"))
283 goto destroy_skel;
284 if (!ASSERT_OK(bpf_map_update_elem(map_fd, sysheap_test_buffer_name, &f, BPF_ANY),
285 "insert sysheap buffer"))
286 goto destroy_skel;
287
288 if (!ASSERT_OK(create_test_buffers(), "create_test_buffers"))
289 goto destroy;
290
291 if (!ASSERT_OK(dmabuf_iter__attach(skel), "skel_attach"))
292 goto destroy;
293
294 if (test__start_subtest("no_infinite_reads"))
295 subtest_dmabuf_iter_check_no_infinite_reads(skel);
296 if (test__start_subtest("default_iter"))
297 subtest_dmabuf_iter_check_default_iter(skel);
298 if (test__start_subtest("lots_of_buffers")) {
299 size_t NUM_BUFS = 100;
300 int buffers[NUM_BUFS];
301 int i;
302
303 for (i = 0; i < NUM_BUFS; ++i) {
304 buffers[i] = create_sys_heap_dmabuf(getpagesize());
305 if (!ASSERT_OK_FD(buffers[i], "dmabuf_fd"))
306 goto cleanup_bufs;
307 }
308
309 subtest_dmabuf_iter_check_lots_of_buffers(skel);
310
311 cleanup_bufs:
312 for (--i; i >= 0; --i)
313 close(buffers[i]);
314 }
315 if (test__start_subtest("open_coded"))
316 subtest_dmabuf_iter_check_open_coded(skel, map_fd);
317
318 destroy:
319 destroy_test_buffers();
320 destroy_skel:
321 dmabuf_iter__destroy(skel);
322 }
323