4459d59b1a24fe8224078298c420f1b6633daa61
[dpdk.git] / lib / eal / windows / eal_memalloc.c
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 }