xref: /dpdk/lib/eal/windows/eal_memalloc.c (revision b19f366c)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright (c) 2020 Dmitry Kozlyuk
3  */
4 
5 #include <rte_errno.h>
6 
7 #include "eal_internal_cfg.h"
8 #include "eal_memalloc.h"
9 #include "eal_memcfg.h"
10 #include "eal_private.h"
11 #include "eal_windows.h"
12 
13 int
14 eal_memalloc_get_seg_fd(int list_idx, int seg_idx)
15 {
16 	/* Hugepages have no associated files in Windows. */
17 	RTE_SET_USED(list_idx);
18 	RTE_SET_USED(seg_idx);
19 	EAL_LOG_NOT_IMPLEMENTED();
20 	return -1;
21 }
22 
23 int
24 eal_memalloc_get_seg_fd_offset(int list_idx, int seg_idx, size_t *offset)
25 {
26 	/* Hugepages have no associated files in Windows. */
27 	RTE_SET_USED(list_idx);
28 	RTE_SET_USED(seg_idx);
29 	RTE_SET_USED(offset);
30 	EAL_LOG_NOT_IMPLEMENTED();
31 	return -1;
32 }
33 
34 static int
35 alloc_seg(struct rte_memseg *ms, void *requested_addr, int socket_id,
36 	struct hugepage_info *hi)
37 {
38 	HANDLE current_process;
39 	unsigned int numa_node;
40 	size_t alloc_sz;
41 	void *addr;
42 	rte_iova_t iova = RTE_BAD_IOVA;
43 	PSAPI_WORKING_SET_EX_INFORMATION info;
44 	PSAPI_WORKING_SET_EX_BLOCK *page;
45 
46 	if (ms->len > 0) {
47 		/* If a segment is already allocated as needed, return it. */
48 		if ((ms->addr == requested_addr) &&
49 			(ms->socket_id == socket_id) &&
50 			(ms->hugepage_sz == hi->hugepage_sz)) {
51 			return 0;
52 		}
53 
54 		/* Bugcheck, should not happen. */
55 		RTE_LOG(DEBUG, EAL, "Attempted to reallocate segment %p "
56 			"(size %zu) on socket %d", ms->addr,
57 			ms->len, ms->socket_id);
58 		return -1;
59 	}
60 
61 	current_process = GetCurrentProcess();
62 	numa_node = eal_socket_numa_node(socket_id);
63 	alloc_sz = hi->hugepage_sz;
64 
65 	if (requested_addr == NULL) {
66 		/* Request a new chunk of memory from OS. */
67 		addr = eal_mem_alloc_socket(alloc_sz, socket_id);
68 		if (addr == NULL) {
69 			RTE_LOG(DEBUG, EAL, "Cannot allocate %zu bytes "
70 				"on socket %d\n", alloc_sz, socket_id);
71 			return -1;
72 		}
73 	} else {
74 		/* Requested address is already reserved, commit memory. */
75 		addr = eal_mem_commit(requested_addr, alloc_sz, socket_id);
76 
77 		/* During commitment, memory is temporary freed and might
78 		 * be allocated by different non-EAL thread. This is a fatal
79 		 * error, because it breaks MSL assumptions.
80 		 */
81 		if ((addr != NULL) && (addr != requested_addr)) {
82 			RTE_LOG(CRIT, EAL, "Address %p occupied by an alien "
83 				" allocation - MSL is not VA-contiguous!\n",
84 				requested_addr);
85 			return -1;
86 		}
87 
88 		if (addr == NULL) {
89 			RTE_LOG(DEBUG, EAL, "Cannot commit reserved memory %p "
90 				"(size %zu) on socket %d\n",
91 				requested_addr, alloc_sz, socket_id);
92 			return -1;
93 		}
94 	}
95 
96 	/* Force OS to allocate a physical page and select a NUMA node.
97 	 * Hugepages are not pageable in Windows, so there's no race
98 	 * for physical address.
99 	 */
100 	*(volatile int *)addr = *(volatile int *)addr;
101 
102 	/* Only try to obtain IOVA if it's available, so that applications
103 	 * that do not need IOVA can use this allocator.
104 	 */
105 	if (rte_eal_using_phys_addrs()) {
106 		iova = rte_mem_virt2iova(addr);
107 		if (iova == RTE_BAD_IOVA) {
108 			RTE_LOG(DEBUG, EAL,
109 				"Cannot get IOVA of allocated segment\n");
110 			goto error;
111 		}
112 	}
113 
114 	/* Only "Ex" function can handle hugepages. */
115 	info.VirtualAddress = addr;
116 	if (!QueryWorkingSetEx(current_process, &info, sizeof(info))) {
117 		RTE_LOG_WIN32_ERR("QueryWorkingSetEx(%p)", addr);
118 		goto error;
119 	}
120 
121 	page = &info.VirtualAttributes;
122 	if (!page->Valid || !page->LargePage) {
123 		RTE_LOG(DEBUG, EAL, "Got regular page instead of a hugepage\n");
124 		goto error;
125 	}
126 	if (page->Node != numa_node) {
127 		RTE_LOG(DEBUG, EAL,
128 			"NUMA node hint %u (socket %d) not respected, got %u\n",
129 			numa_node, socket_id, page->Node);
130 		goto error;
131 	}
132 
133 	ms->addr = addr;
134 	ms->hugepage_sz = hi->hugepage_sz;
135 	ms->len = alloc_sz;
136 	ms->nchannel = rte_memory_get_nchannel();
137 	ms->nrank = rte_memory_get_nrank();
138 	ms->iova = iova;
139 	ms->socket_id = socket_id;
140 
141 	return 0;
142 
143 error:
144 	/* Only jump here when `addr` and `alloc_sz` are valid. */
145 	if (eal_mem_decommit(addr, alloc_sz) && (rte_errno == EADDRNOTAVAIL)) {
146 		/* During decommitment, memory is temporarily returned
147 		 * to the system and the address may become unavailable.
148 		 */
149 		RTE_LOG(CRIT, EAL, "Address %p occupied by an alien "
150 			" allocation - MSL is not VA-contiguous!\n", addr);
151 	}
152 	return -1;
153 }
154 
155 static int
156 free_seg(struct rte_memseg *ms)
157 {
158 	if (eal_mem_decommit(ms->addr, ms->len)) {
159 		if (rte_errno == EADDRNOTAVAIL) {
160 			/* See alloc_seg() for explanation. */
161 			RTE_LOG(CRIT, EAL, "Address %p occupied by an alien "
162 				" allocation - MSL is not VA-contiguous!\n",
163 				ms->addr);
164 		}
165 		return -1;
166 	}
167 
168 	/* Must clear the segment, because alloc_seg() inspects it. */
169 	memset(ms, 0, sizeof(*ms));
170 	return 0;
171 }
172 
173 struct alloc_walk_param {
174 	struct hugepage_info *hi;
175 	struct rte_memseg **ms;
176 	size_t page_sz;
177 	unsigned int segs_allocated;
178 	unsigned int n_segs;
179 	int socket;
180 	bool exact;
181 };
182 
183 static int
184 alloc_seg_walk(const struct rte_memseg_list *msl, void *arg)
185 {
186 	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
187 	struct alloc_walk_param *wa = arg;
188 	struct rte_memseg_list *cur_msl;
189 	size_t page_sz;
190 	int cur_idx, start_idx, j;
191 	unsigned int msl_idx, need, i;
192 
193 	if (msl->page_sz != wa->page_sz)
194 		return 0;
195 	if (msl->socket_id != wa->socket)
196 		return 0;
197 
198 	page_sz = (size_t)msl->page_sz;
199 
200 	msl_idx = msl - mcfg->memsegs;
201 	cur_msl = &mcfg->memsegs[msl_idx];
202 
203 	need = wa->n_segs;
204 
205 	/* try finding space in memseg list */
206 	if (wa->exact) {
207 		/* if we require exact number of pages in a list, find them */
208 		cur_idx = rte_fbarray_find_next_n_free(
209 			&cur_msl->memseg_arr, 0, need);
210 		if (cur_idx < 0)
211 			return 0;
212 		start_idx = cur_idx;
213 	} else {
214 		int cur_len;
215 
216 		/* we don't require exact number of pages, so we're going to go
217 		 * for best-effort allocation. that means finding the biggest
218 		 * unused block, and going with that.
219 		 */
220 		cur_idx = rte_fbarray_find_biggest_free(
221 			&cur_msl->memseg_arr, 0);
222 		if (cur_idx < 0)
223 			return 0;
224 		start_idx = cur_idx;
225 		/* adjust the size to possibly be smaller than original
226 		 * request, but do not allow it to be bigger.
227 		 */
228 		cur_len = rte_fbarray_find_contig_free(
229 			&cur_msl->memseg_arr, cur_idx);
230 		need = RTE_MIN(need, (unsigned int)cur_len);
231 	}
232 
233 	for (i = 0; i < need; i++, cur_idx++) {
234 		struct rte_memseg *cur;
235 		void *map_addr;
236 
237 		cur = rte_fbarray_get(&cur_msl->memseg_arr, cur_idx);
238 		map_addr = RTE_PTR_ADD(cur_msl->base_va, cur_idx * page_sz);
239 
240 		if (alloc_seg(cur, map_addr, wa->socket, wa->hi)) {
241 			RTE_LOG(DEBUG, EAL, "attempted to allocate %i segments, "
242 				"but only %i were allocated\n", need, i);
243 
244 			/* if exact number wasn't requested, stop */
245 			if (!wa->exact)
246 				goto out;
247 
248 			/* clean up */
249 			for (j = start_idx; j < cur_idx; j++) {
250 				struct rte_memseg *tmp;
251 				struct rte_fbarray *arr = &cur_msl->memseg_arr;
252 
253 				tmp = rte_fbarray_get(arr, j);
254 				rte_fbarray_set_free(arr, j);
255 
256 				if (free_seg(tmp))
257 					RTE_LOG(DEBUG, EAL, "Cannot free page\n");
258 			}
259 			/* clear the list */
260 			if (wa->ms)
261 				memset(wa->ms, 0, sizeof(*wa->ms) * wa->n_segs);
262 
263 			return -1;
264 		}
265 		if (wa->ms)
266 			wa->ms[i] = cur;
267 
268 		rte_fbarray_set_used(&cur_msl->memseg_arr, cur_idx);
269 	}
270 
271 out:
272 	wa->segs_allocated = i;
273 	if (i > 0)
274 		cur_msl->version++;
275 
276 	/* if we didn't allocate any segments, move on to the next list */
277 	return i > 0;
278 }
279 
280 struct free_walk_param {
281 	struct hugepage_info *hi;
282 	struct rte_memseg *ms;
283 };
284 static int
285 free_seg_walk(const struct rte_memseg_list *msl, void *arg)
286 {
287 	struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
288 	struct rte_memseg_list *found_msl;
289 	struct free_walk_param *wa = arg;
290 	uintptr_t start_addr, end_addr;
291 	int msl_idx, seg_idx, ret;
292 
293 	start_addr = (uintptr_t) msl->base_va;
294 	end_addr = start_addr + msl->len;
295 
296 	if ((uintptr_t)wa->ms->addr < start_addr ||
297 		(uintptr_t)wa->ms->addr >= end_addr)
298 		return 0;
299 
300 	msl_idx = msl - mcfg->memsegs;
301 	seg_idx = RTE_PTR_DIFF(wa->ms->addr, start_addr) / msl->page_sz;
302 
303 	/* msl is const */
304 	found_msl = &mcfg->memsegs[msl_idx];
305 	found_msl->version++;
306 
307 	rte_fbarray_set_free(&found_msl->memseg_arr, seg_idx);
308 
309 	ret = free_seg(wa->ms);
310 
311 	return (ret < 0) ? (-1) : 1;
312 }
313 
314 int
315 eal_memalloc_alloc_seg_bulk(struct rte_memseg **ms, int n_segs,
316 		size_t page_sz, int socket, bool exact)
317 {
318 	unsigned int i;
319 	int ret = -1;
320 	struct alloc_walk_param wa;
321 	struct hugepage_info *hi = NULL;
322 	struct internal_config *internal_conf =
323 		eal_get_internal_configuration();
324 
325 	if (internal_conf->legacy_mem) {
326 		RTE_LOG(ERR, EAL, "dynamic allocation not supported in legacy mode\n");
327 		return -ENOTSUP;
328 	}
329 
330 	for (i = 0; i < internal_conf->num_hugepage_sizes; i++) {
331 		struct hugepage_info *hpi = &internal_conf->hugepage_info[i];
332 		if (page_sz == hpi->hugepage_sz) {
333 			hi = hpi;
334 			break;
335 		}
336 	}
337 	if (!hi) {
338 		RTE_LOG(ERR, EAL, "cannot find relevant hugepage_info entry\n");
339 		return -1;
340 	}
341 
342 	memset(&wa, 0, sizeof(wa));
343 	wa.exact = exact;
344 	wa.hi = hi;
345 	wa.ms = ms;
346 	wa.n_segs = n_segs;
347 	wa.page_sz = page_sz;
348 	wa.socket = socket;
349 	wa.segs_allocated = 0;
350 
351 	/* memalloc is locked, so it's safe to use thread-unsafe version */
352 	ret = rte_memseg_list_walk_thread_unsafe(alloc_seg_walk, &wa);
353 	if (ret == 0) {
354 		RTE_LOG(ERR, EAL, "cannot find suitable memseg_list\n");
355 		ret = -1;
356 	} else if (ret > 0) {
357 		ret = (int)wa.segs_allocated;
358 	}
359 
360 	return ret;
361 }
362 
363 struct rte_memseg *
364 eal_memalloc_alloc_seg(size_t page_sz, int socket)
365 {
366 	struct rte_memseg *ms = NULL;
367 	eal_memalloc_alloc_seg_bulk(&ms, 1, page_sz, socket, true);
368 	return ms;
369 }
370 
371 int
372 eal_memalloc_free_seg_bulk(struct rte_memseg **ms, int n_segs)
373 {
374 	int seg, ret = 0;
375 	struct internal_config *internal_conf =
376 		eal_get_internal_configuration();
377 
378 	/* dynamic free not supported in legacy mode */
379 	if (internal_conf->legacy_mem)
380 		return -1;
381 
382 	for (seg = 0; seg < n_segs; seg++) {
383 		struct rte_memseg *cur = ms[seg];
384 		struct hugepage_info *hi = NULL;
385 		struct free_walk_param wa;
386 		size_t i;
387 		int walk_res;
388 
389 		/* if this page is marked as unfreeable, fail */
390 		if (cur->flags & RTE_MEMSEG_FLAG_DO_NOT_FREE) {
391 			RTE_LOG(DEBUG, EAL, "Page is not allowed to be freed\n");
392 			ret = -1;
393 			continue;
394 		}
395 
396 		memset(&wa, 0, sizeof(wa));
397 
398 		for (i = 0; i < RTE_DIM(internal_conf->hugepage_info); i++) {
399 			hi = &internal_conf->hugepage_info[i];
400 			if (cur->hugepage_sz == hi->hugepage_sz)
401 				break;
402 		}
403 		if (i == RTE_DIM(internal_conf->hugepage_info)) {
404 			RTE_LOG(ERR, EAL, "Can't find relevant hugepage_info entry\n");
405 			ret = -1;
406 			continue;
407 		}
408 
409 		wa.ms = cur;
410 		wa.hi = hi;
411 
412 		/* memalloc is locked, so it's safe to use thread-unsafe version
413 		 */
414 		walk_res = rte_memseg_list_walk_thread_unsafe(free_seg_walk,
415 				&wa);
416 		if (walk_res == 1)
417 			continue;
418 		if (walk_res == 0)
419 			RTE_LOG(ERR, EAL, "Couldn't find memseg list\n");
420 		ret = -1;
421 	}
422 	return ret;
423 }
424 
425 int
426 eal_memalloc_free_seg(struct rte_memseg *ms)
427 {
428 	return eal_memalloc_free_seg_bulk(&ms, 1);
429 }
430 
431 int
432 eal_memalloc_sync_with_primary(void)
433 {
434 	/* No multi-process support. */
435 	EAL_LOG_NOT_IMPLEMENTED();
436 	return -1;
437 }
438 
439 int
440 eal_memalloc_cleanup(void)
441 {
442 	/* not implemented */
443 	return 0;
444 }
445 
446 int
447 eal_memalloc_init(void)
448 {
449 	/* No action required. */
450 	return 0;
451 }
452