xref: /dpdk/app/test/test_external_mem.c (revision 3c60274c0995a7a74c5550d2f5bbcfbd9d548515)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2018 Intel Corporation
3  */
4 
5 #include "test.h"
6 
7 #include <errno.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <fcntl.h>
12 
13 #ifdef RTE_EXEC_ENV_WINDOWS
14 static int
test_external_mem(void)15 test_external_mem(void)
16 {
17 	printf("external_mem not supported on Windows, skipping test\n");
18 	return TEST_SKIPPED;
19 }
20 
21 #else
22 
23 #include <sys/mman.h>
24 #include <sys/wait.h>
25 
26 #include <rte_common.h>
27 #include <rte_debug.h>
28 #include <rte_eal.h>
29 #include <rte_eal_paging.h>
30 #include <rte_errno.h>
31 #include <rte_malloc.h>
32 #include <rte_ring.h>
33 #include <rte_string_fns.h>
34 
35 #define EXTERNAL_MEM_SZ (RTE_PGSIZE_4K << 10) /* 4M of data */
36 
37 static int
check_mem(void * addr,rte_iova_t * iova,size_t pgsz,int n_pages)38 check_mem(void *addr, rte_iova_t *iova, size_t pgsz, int n_pages)
39 {
40 	int i;
41 
42 	/* check that we can get this memory from EAL now */
43 	for (i = 0; i < n_pages; i++) {
44 		const struct rte_memseg_list *msl;
45 		const struct rte_memseg *ms;
46 		void *cur = RTE_PTR_ADD(addr, pgsz * i);
47 		rte_iova_t expected_iova;
48 
49 		msl = rte_mem_virt2memseg_list(cur);
50 		if (!msl->external) {
51 			printf("%s():%i: Memseg list is not marked as external\n",
52 				__func__, __LINE__);
53 			return -1;
54 		}
55 
56 		ms = rte_mem_virt2memseg(cur, msl);
57 		if (ms == NULL) {
58 			printf("%s():%i: Failed to retrieve memseg for external mem\n",
59 				__func__, __LINE__);
60 			return -1;
61 		}
62 		if (ms->addr != cur) {
63 			printf("%s():%i: VA mismatch\n", __func__, __LINE__);
64 			return -1;
65 		}
66 		expected_iova = (iova == NULL) ? RTE_BAD_IOVA : iova[i];
67 		if (ms->iova != expected_iova) {
68 			printf("%s():%i: IOVA mismatch\n", __func__, __LINE__);
69 			return -1;
70 		}
71 	}
72 	return 0;
73 }
74 
75 static int
test_malloc_invalid_param(void * addr,size_t len,size_t pgsz,rte_iova_t * iova,int n_pages)76 test_malloc_invalid_param(void *addr, size_t len, size_t pgsz, rte_iova_t *iova,
77 		int n_pages)
78 {
79 	static const char * const names[] = {
80 		NULL, /* NULL name */
81 		"",   /* empty name */
82 		"this heap name is definitely way too long to be valid"
83 	};
84 	const char *valid_name = "valid heap name";
85 	unsigned int i;
86 
87 	/* check invalid name handling */
88 	for (i = 0; i < RTE_DIM(names); i++) {
89 		const char *name = names[i];
90 
91 		/* these calls may fail for other reasons, so check errno */
92 		if (rte_malloc_heap_create(name) >= 0 || rte_errno != EINVAL) {
93 			printf("%s():%i: Created heap with invalid name\n",
94 					__func__, __LINE__);
95 			goto fail;
96 		}
97 
98 		if (rte_malloc_heap_destroy(name) >= 0 || rte_errno != EINVAL) {
99 			printf("%s():%i: Destroyed heap with invalid name\n",
100 					__func__, __LINE__);
101 			goto fail;
102 		}
103 
104 		if (rte_malloc_heap_get_socket(name) >= 0 ||
105 				rte_errno != EINVAL) {
106 			printf("%s():%i: Found socket for heap with invalid name\n",
107 					__func__, __LINE__);
108 			goto fail;
109 		}
110 
111 		if (rte_malloc_heap_memory_add(name, addr, len,
112 				NULL, 0, pgsz) >= 0 || rte_errno != EINVAL) {
113 			printf("%s():%i: Added memory to heap with invalid name\n",
114 					__func__, __LINE__);
115 			goto fail;
116 		}
117 		if (rte_malloc_heap_memory_remove(name, addr, len) >= 0 ||
118 				rte_errno != EINVAL) {
119 			printf("%s():%i: Removed memory from heap with invalid name\n",
120 					__func__, __LINE__);
121 			goto fail;
122 		}
123 
124 		if (rte_malloc_heap_memory_attach(name, addr, len) >= 0 ||
125 				rte_errno != EINVAL) {
126 			printf("%s():%i: Attached memory to heap with invalid name\n",
127 				__func__, __LINE__);
128 			goto fail;
129 		}
130 		if (rte_malloc_heap_memory_detach(name, addr, len) >= 0 ||
131 				rte_errno != EINVAL) {
132 			printf("%s():%i: Detached memory from heap with invalid name\n",
133 				__func__, __LINE__);
134 			goto fail;
135 		}
136 	}
137 
138 	/* do same as above, but with a valid heap name */
139 
140 	/* skip create call */
141 	if (rte_malloc_heap_destroy(valid_name) >= 0 || rte_errno != ENOENT) {
142 		printf("%s():%i: Destroyed heap with invalid name\n",
143 			__func__, __LINE__);
144 		goto fail;
145 	}
146 	if (rte_malloc_heap_get_socket(valid_name) >= 0 ||
147 			rte_errno != ENOENT) {
148 		printf("%s():%i: Found socket for heap with invalid name\n",
149 				__func__, __LINE__);
150 		goto fail;
151 	}
152 
153 	/* these calls may fail for other reasons, so check errno */
154 	if (rte_malloc_heap_memory_add(valid_name, addr, len,
155 			NULL, 0, pgsz) >= 0 || rte_errno != ENOENT) {
156 		printf("%s():%i: Added memory to non-existent heap\n",
157 			__func__, __LINE__);
158 		goto fail;
159 	}
160 	if (rte_malloc_heap_memory_remove(valid_name, addr, len) >= 0 ||
161 			rte_errno != ENOENT) {
162 		printf("%s():%i: Removed memory from non-existent heap\n",
163 			__func__, __LINE__);
164 		goto fail;
165 	}
166 
167 	if (rte_malloc_heap_memory_attach(valid_name, addr, len) >= 0 ||
168 			rte_errno != ENOENT) {
169 		printf("%s():%i: Attached memory to non-existent heap\n",
170 			__func__, __LINE__);
171 		goto fail;
172 	}
173 	if (rte_malloc_heap_memory_detach(valid_name, addr, len) >= 0 ||
174 			rte_errno != ENOENT) {
175 		printf("%s():%i: Detached memory from non-existent heap\n",
176 			__func__, __LINE__);
177 		goto fail;
178 	}
179 
180 	/* create a valid heap but test other invalid parameters */
181 	if (rte_malloc_heap_create(valid_name) != 0) {
182 		printf("%s():%i: Failed to create valid heap\n",
183 			__func__, __LINE__);
184 		goto fail;
185 	}
186 
187 	/* zero length */
188 	if (rte_malloc_heap_memory_add(valid_name, addr, 0,
189 			NULL, 0, pgsz) >= 0 || rte_errno != EINVAL) {
190 		printf("%s():%i: Added memory with invalid parameters\n",
191 			__func__, __LINE__);
192 		goto fail;
193 	}
194 
195 	if (rte_malloc_heap_memory_remove(valid_name, addr, 0) >= 0 ||
196 			rte_errno != EINVAL) {
197 		printf("%s():%i: Removed memory with invalid parameters\n",
198 			__func__, __LINE__);
199 		goto fail;
200 	}
201 
202 	if (rte_malloc_heap_memory_attach(valid_name, addr, 0) >= 0 ||
203 			rte_errno != EINVAL) {
204 		printf("%s():%i: Attached memory with invalid parameters\n",
205 			__func__, __LINE__);
206 		goto fail;
207 	}
208 	if (rte_malloc_heap_memory_detach(valid_name, addr, 0) >= 0 ||
209 			rte_errno != EINVAL) {
210 		printf("%s():%i: Detached memory with invalid parameters\n",
211 			__func__, __LINE__);
212 		goto fail;
213 	}
214 
215 	/* zero address */
216 	if (rte_malloc_heap_memory_add(valid_name, NULL, len,
217 			NULL, 0, pgsz) >= 0 || rte_errno != EINVAL) {
218 		printf("%s():%i: Added memory with invalid parameters\n",
219 			__func__, __LINE__);
220 		goto fail;
221 	}
222 
223 	if (rte_malloc_heap_memory_remove(valid_name, NULL, len) >= 0 ||
224 			rte_errno != EINVAL) {
225 		printf("%s():%i: Removed memory with invalid parameters\n",
226 			__func__, __LINE__);
227 		goto fail;
228 	}
229 
230 	if (rte_malloc_heap_memory_attach(valid_name, NULL, len) >= 0 ||
231 			rte_errno != EINVAL) {
232 		printf("%s():%i: Attached memory with invalid parameters\n",
233 			__func__, __LINE__);
234 		goto fail;
235 	}
236 	if (rte_malloc_heap_memory_detach(valid_name, NULL, len) >= 0 ||
237 			rte_errno != EINVAL) {
238 		printf("%s():%i: Detached memory with invalid parameters\n",
239 			__func__, __LINE__);
240 		goto fail;
241 	}
242 
243 	/* the following tests are only valid if IOVA table is not NULL */
244 	if (iova != NULL) {
245 		/* wrong page count */
246 		if (rte_malloc_heap_memory_add(valid_name, addr, len,
247 				iova, 0, pgsz) >= 0 || rte_errno != EINVAL) {
248 			printf("%s():%i: Added memory with invalid parameters\n",
249 				__func__, __LINE__);
250 			goto fail;
251 		}
252 		if (rte_malloc_heap_memory_add(valid_name, addr, len,
253 				iova, n_pages - 1, pgsz) >= 0 ||
254 				rte_errno != EINVAL) {
255 			printf("%s():%i: Added memory with invalid parameters\n",
256 				__func__, __LINE__);
257 			goto fail;
258 		}
259 		if (rte_malloc_heap_memory_add(valid_name, addr, len,
260 				iova, n_pages + 1, pgsz) >= 0 ||
261 				rte_errno != EINVAL) {
262 			printf("%s():%i: Added memory with invalid parameters\n",
263 				__func__, __LINE__);
264 			goto fail;
265 		}
266 	}
267 
268 	/* tests passed, destroy heap */
269 	if (rte_malloc_heap_destroy(valid_name) != 0) {
270 		printf("%s():%i: Failed to destroy valid heap\n",
271 			__func__, __LINE__);
272 		goto fail;
273 	}
274 	return 0;
275 fail:
276 	rte_malloc_heap_destroy(valid_name);
277 	return -1;
278 }
279 
280 static int
test_malloc_basic(void * addr,size_t len,size_t pgsz,rte_iova_t * iova,int n_pages)281 test_malloc_basic(void *addr, size_t len, size_t pgsz, rte_iova_t *iova,
282 		int n_pages)
283 {
284 	const char *heap_name = "heap";
285 	void *ptr = NULL;
286 	int socket_id;
287 	const struct rte_memzone *mz = NULL, *contig_mz = NULL;
288 
289 	/* create heap */
290 	if (rte_malloc_heap_create(heap_name) != 0) {
291 		printf("%s():%i: Failed to create malloc heap\n",
292 			__func__, __LINE__);
293 		goto fail;
294 	}
295 
296 	/* get socket ID corresponding to this heap */
297 	socket_id = rte_malloc_heap_get_socket(heap_name);
298 	if (socket_id < 0) {
299 		printf("%s():%i: cannot find socket for external heap\n",
300 			__func__, __LINE__);
301 		goto fail;
302 	}
303 
304 	/* heap is empty, so any allocation should fail */
305 	ptr = rte_malloc_socket("EXTMEM", 64, 0, socket_id);
306 	if (ptr != NULL) {
307 		printf("%s():%i: Allocated from empty heap\n", __func__,
308 			__LINE__);
309 		goto fail;
310 	}
311 
312 	/* add memory to heap */
313 	if (rte_malloc_heap_memory_add(heap_name, addr, len,
314 			iova, n_pages, pgsz) != 0) {
315 		printf("%s():%i: Failed to add memory to heap\n",
316 			__func__, __LINE__);
317 		goto fail;
318 	}
319 
320 	/* check if memory is accessible from EAL */
321 	if (check_mem(addr, iova, pgsz, n_pages) < 0)
322 		goto fail;
323 
324 	/* allocate - this now should succeed */
325 	ptr = rte_malloc_socket("EXTMEM", 64, 0, socket_id);
326 	if (ptr == NULL) {
327 		printf("%s():%i: Failed to allocate from external heap\n",
328 			__func__, __LINE__);
329 		goto fail;
330 	}
331 
332 	/* check if address is in expected range */
333 	if (ptr < addr || ptr >= RTE_PTR_ADD(addr, len)) {
334 		printf("%s():%i: Allocated from unexpected address space\n",
335 			__func__, __LINE__);
336 		goto fail;
337 	}
338 
339 	/* we've allocated something - removing memory should fail */
340 	if (rte_malloc_heap_memory_remove(heap_name, addr, len) >= 0 ||
341 			rte_errno != EBUSY) {
342 		printf("%s():%i: Removing memory succeeded when memory is not free\n",
343 			__func__, __LINE__);
344 		goto fail;
345 	}
346 	if (rte_malloc_heap_destroy(heap_name) >= 0 || rte_errno != EBUSY) {
347 		printf("%s():%i: Destroying heap succeeded when memory is not free\n",
348 			__func__, __LINE__);
349 		goto fail;
350 	}
351 
352 	/* try allocating a memzone */
353 	mz = rte_memzone_reserve("heap_test", pgsz * 2, socket_id, 0);
354 	if (mz == NULL) {
355 		printf("%s():%i: Failed to reserve memzone\n",
356 			__func__, __LINE__);
357 		goto fail;
358 	}
359 	/* try allocating an IOVA-contiguous memzone - this should succeed
360 	 * if we've set up a contiguous IOVA table, and fail if we haven't.
361 	 */
362 	contig_mz = rte_memzone_reserve("heap_test_contig", pgsz * 2, socket_id,
363 			RTE_MEMZONE_IOVA_CONTIG);
364 	if ((iova == NULL) != (contig_mz == NULL)) {
365 		printf("%s():%i: Failed to reserve memzone\n",
366 			__func__, __LINE__);
367 		goto fail;
368 	}
369 
370 	rte_malloc_dump_stats(stdout, NULL);
371 	rte_malloc_dump_heaps(stdout);
372 
373 	/* free memory - removing it should now succeed */
374 	rte_free(ptr);
375 	ptr = NULL;
376 
377 	rte_memzone_free(mz);
378 	mz = NULL;
379 	rte_memzone_free(contig_mz);
380 	contig_mz = NULL;
381 
382 	if (rte_malloc_heap_memory_remove(heap_name, addr, len) != 0) {
383 		printf("%s():%i: Removing memory from heap failed\n",
384 			__func__, __LINE__);
385 		goto fail;
386 	}
387 	if (rte_malloc_heap_destroy(heap_name) != 0) {
388 		printf("%s():%i: Destroying heap failed\n",
389 			__func__, __LINE__);
390 		goto fail;
391 	}
392 
393 	return 0;
394 fail:
395 	rte_memzone_free(contig_mz);
396 	rte_memzone_free(mz);
397 	rte_free(ptr);
398 	/* even if something failed, attempt to clean up */
399 	rte_malloc_heap_memory_remove(heap_name, addr, len);
400 	rte_malloc_heap_destroy(heap_name);
401 
402 	return -1;
403 }
404 
405 static int
test_extmem_invalid_param(void * addr,size_t len,size_t pgsz,rte_iova_t * iova,int n_pages)406 test_extmem_invalid_param(void *addr, size_t len, size_t pgsz, rte_iova_t *iova,
407 		int n_pages)
408 {
409 	/* these calls may fail for other reasons, so check errno */
410 	if (rte_extmem_unregister(addr, len) >= 0 ||
411 			rte_errno != ENOENT) {
412 		printf("%s():%i: Unregistered non-existent memory\n",
413 			__func__, __LINE__);
414 		return -1;
415 	}
416 
417 	if (rte_extmem_attach(addr, len) >= 0 ||
418 			rte_errno != ENOENT) {
419 		printf("%s():%i: Attached to non-existent memory\n",
420 			__func__, __LINE__);
421 		return -1;
422 	}
423 	if (rte_extmem_attach(addr, len) >= 0 ||
424 			rte_errno != ENOENT) {
425 		printf("%s():%i: Detached from non-existent memory\n",
426 			__func__, __LINE__);
427 		return -1;
428 	}
429 
430 	/* zero length */
431 	if (rte_extmem_register(addr, 0, NULL, 0, pgsz) >= 0 ||
432 			rte_errno != EINVAL) {
433 		printf("%s():%i: Registered memory with invalid parameters\n",
434 			__func__, __LINE__);
435 		return -1;
436 	}
437 
438 	if (rte_extmem_unregister(addr, 0) >= 0 ||
439 			rte_errno != EINVAL) {
440 		printf("%s():%i: Unregistered memory with invalid parameters\n",
441 			__func__, __LINE__);
442 		return -1;
443 	}
444 
445 	if (rte_extmem_attach(addr, 0) >= 0 ||
446 			rte_errno != EINVAL) {
447 		printf("%s():%i: Attached memory with invalid parameters\n",
448 			__func__, __LINE__);
449 		return -1;
450 	}
451 	if (rte_extmem_attach(addr, 0) >= 0 ||
452 			rte_errno != EINVAL) {
453 		printf("%s():%i: Detached memory with invalid parameters\n",
454 			__func__, __LINE__);
455 		return -1;
456 	}
457 
458 	/* zero address */
459 	if (rte_extmem_register(NULL, len, NULL, 0, pgsz) >= 0 ||
460 			rte_errno != EINVAL) {
461 		printf("%s():%i: Registered memory with invalid parameters\n",
462 			__func__, __LINE__);
463 		return -1;
464 	}
465 
466 	if (rte_extmem_unregister(NULL, len) >= 0 ||
467 			rte_errno != EINVAL) {
468 		printf("%s():%i: Unregistered memory with invalid parameters\n",
469 			__func__, __LINE__);
470 		return -1;
471 	}
472 
473 	if (rte_extmem_attach(NULL, len) >= 0 ||
474 			rte_errno != EINVAL) {
475 		printf("%s():%i: Attached memory with invalid parameters\n",
476 			__func__, __LINE__);
477 		return -1;
478 	}
479 	if (rte_extmem_attach(NULL, len) >= 0 ||
480 			rte_errno != EINVAL) {
481 		printf("%s():%i: Detached memory with invalid parameters\n",
482 			__func__, __LINE__);
483 		return -1;
484 	}
485 
486 	/* the following tests are only valid if IOVA table is not NULL */
487 	if (iova != NULL) {
488 		/* wrong page count */
489 		if (rte_extmem_register(addr, len,
490 				iova, 0, pgsz) >= 0 || rte_errno != EINVAL) {
491 			printf("%s():%i: Registered memory with invalid parameters\n",
492 				__func__, __LINE__);
493 			return -1;
494 		}
495 		if (rte_extmem_register(addr, len,
496 				iova, n_pages - 1, pgsz) >= 0 ||
497 				rte_errno != EINVAL) {
498 			printf("%s():%i: Registered memory with invalid parameters\n",
499 				__func__, __LINE__);
500 			return -1;
501 		}
502 		if (rte_extmem_register(addr, len,
503 				iova, n_pages + 1, pgsz) >= 0 ||
504 				rte_errno != EINVAL) {
505 			printf("%s():%i: Registered memory with invalid parameters\n",
506 				__func__, __LINE__);
507 			return -1;
508 		}
509 	}
510 
511 	return 0;
512 }
513 
514 static int
test_extmem_basic(void * addr,size_t len,size_t pgsz,rte_iova_t * iova,int n_pages)515 test_extmem_basic(void *addr, size_t len, size_t pgsz, rte_iova_t *iova,
516 		int n_pages)
517 {
518 	/* register memory */
519 	if (rte_extmem_register(addr, len, iova, n_pages, pgsz) != 0) {
520 		printf("%s():%i: Failed to register memory\n",
521 			__func__, __LINE__);
522 		goto fail;
523 	}
524 
525 	/* check if memory is accessible from EAL */
526 	if (check_mem(addr, iova, pgsz, n_pages) < 0)
527 		goto fail;
528 
529 	if (rte_extmem_unregister(addr, len) != 0) {
530 		printf("%s():%i: Removing memory from heap failed\n",
531 			__func__, __LINE__);
532 		goto fail;
533 	}
534 
535 	return 0;
536 fail:
537 	/* even if something failed, attempt to clean up */
538 	rte_extmem_unregister(addr, len);
539 
540 	return -1;
541 }
542 
543 /* we need to test attach/detach in secondary processes. */
544 static int
test_external_mem(void)545 test_external_mem(void)
546 {
547 	size_t pgsz = rte_mem_page_size();
548 	size_t len = EXTERNAL_MEM_SZ;
549 	rte_iova_t iova[len / pgsz];
550 	void *addr;
551 	int ret, n_pages;
552 	int i;
553 
554 	/* create external memory area */
555 	n_pages = RTE_DIM(iova);
556 	addr = mmap(NULL, len, PROT_WRITE | PROT_READ,
557 			MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
558 	if (addr == MAP_FAILED) {
559 		printf("%s():%i: Failed to create dummy memory area\n",
560 			__func__, __LINE__);
561 		return -1;
562 	}
563 	for (i = 0; i < n_pages; i++) {
564 		/* arbitrary IOVA */
565 		rte_iova_t tmp = 0x100000000 + i * pgsz;
566 		iova[i] = tmp;
567 	}
568 
569 	/* test external heap memory */
570 	ret = test_malloc_invalid_param(addr, len, pgsz, iova, n_pages);
571 	ret |= test_malloc_basic(addr, len, pgsz, iova, n_pages);
572 	/* when iova table is NULL, everything should still work */
573 	ret |= test_malloc_invalid_param(addr, len, pgsz, NULL, n_pages);
574 	ret |= test_malloc_basic(addr, len, pgsz, NULL, n_pages);
575 
576 	/* test non-heap memory */
577 	ret |= test_extmem_invalid_param(addr, len, pgsz, iova, n_pages);
578 	ret |= test_extmem_basic(addr, len, pgsz, iova, n_pages);
579 	/* when iova table is NULL, everything should still work */
580 	ret |= test_extmem_invalid_param(addr, len, pgsz, NULL, n_pages);
581 	ret |= test_extmem_basic(addr, len, pgsz, NULL, n_pages);
582 
583 	munmap(addr, len);
584 
585 	return ret;
586 }
587 
588 #endif /* !RTE_EXEC_ENV_WINDOWS */
589 
590 REGISTER_TEST_COMMAND(external_mem_autotest, test_external_mem);
591