Line data Source code
1 : /* SPDX-License-Identifier: BSD-3-Clause
2 : * Copyright (c) Intel Corporation. All rights reserved.
3 : * Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4 : */
5 :
6 : #include "spdk_internal/rdma_utils.h"
7 :
8 : #include "spdk/log.h"
9 : #include "spdk/string.h"
10 : #include "spdk/likely.h"
11 : #include "spdk/net.h"
12 : #include "spdk/file.h"
13 :
14 : #include "spdk_internal/assert.h"
15 :
16 : #include <rdma/rdma_cma.h>
17 : #include <rdma/rdma_verbs.h>
18 :
19 : struct rdma_utils_device {
20 : struct ibv_pd *pd;
21 : struct ibv_context *context;
22 : int ref;
23 : bool removed;
24 : TAILQ_ENTRY(rdma_utils_device) tailq;
25 : };
26 :
27 : struct spdk_rdma_utils_mem_map {
28 : struct spdk_mem_map *map;
29 : struct ibv_pd *pd;
30 : struct spdk_nvme_rdma_hooks *hooks;
31 : uint32_t ref_count;
32 : uint32_t access_flags;
33 : LIST_ENTRY(spdk_rdma_utils_mem_map) link;
34 : };
35 :
36 : struct rdma_utils_memory_domain {
37 : TAILQ_ENTRY(rdma_utils_memory_domain) link;
38 : uint32_t ref;
39 : enum spdk_dma_device_type type;
40 : struct ibv_pd *pd;
41 : struct spdk_memory_domain *domain;
42 : struct spdk_memory_domain_rdma_ctx rdma_ctx;
43 : };
44 :
45 : static pthread_mutex_t g_dev_mutex = PTHREAD_MUTEX_INITIALIZER;
46 : static struct ibv_context **g_ctx_list = NULL;
47 : static TAILQ_HEAD(, rdma_utils_device) g_dev_list = TAILQ_HEAD_INITIALIZER(g_dev_list);
48 :
49 : static LIST_HEAD(, spdk_rdma_utils_mem_map) g_rdma_utils_mr_maps = LIST_HEAD_INITIALIZER(
50 : &g_rdma_utils_mr_maps);
51 : static pthread_mutex_t g_rdma_mr_maps_mutex = PTHREAD_MUTEX_INITIALIZER;
52 :
53 : static TAILQ_HEAD(, rdma_utils_memory_domain) g_memory_domains = TAILQ_HEAD_INITIALIZER(
54 : g_memory_domains);
55 : static pthread_mutex_t g_memory_domains_lock = PTHREAD_MUTEX_INITIALIZER;
56 :
57 : static int
58 0 : rdma_utils_mem_notify(void *cb_ctx, struct spdk_mem_map *map,
59 : enum spdk_mem_map_notify_action action,
60 : void *vaddr, size_t size)
61 : {
62 0 : struct spdk_rdma_utils_mem_map *rmap = cb_ctx;
63 0 : struct ibv_pd *pd = rmap->pd;
64 : struct ibv_mr *mr;
65 : uint32_t access_flags;
66 : int rc;
67 :
68 0 : switch (action) {
69 0 : case SPDK_MEM_MAP_NOTIFY_REGISTER:
70 0 : if (rmap->hooks && rmap->hooks->get_rkey) {
71 0 : rc = spdk_mem_map_set_translation(map, (uint64_t)vaddr, size,
72 0 : rmap->hooks->get_rkey(pd, vaddr, size));
73 : } else {
74 0 : access_flags = rmap->access_flags;
75 : #ifdef IBV_ACCESS_OPTIONAL_FIRST
76 0 : access_flags |= IBV_ACCESS_RELAXED_ORDERING;
77 : #endif
78 0 : mr = ibv_reg_mr(pd, vaddr, size, access_flags);
79 0 : if (mr == NULL) {
80 0 : SPDK_ERRLOG("ibv_reg_mr() failed\n");
81 0 : return -1;
82 : } else {
83 0 : rc = spdk_mem_map_set_translation(map, (uint64_t)vaddr, size, (uint64_t)mr);
84 : }
85 : }
86 0 : break;
87 0 : case SPDK_MEM_MAP_NOTIFY_UNREGISTER:
88 0 : if (rmap->hooks == NULL || rmap->hooks->get_rkey == NULL) {
89 0 : mr = (struct ibv_mr *)spdk_mem_map_translate(map, (uint64_t)vaddr, NULL);
90 0 : if (mr) {
91 0 : ibv_dereg_mr(mr);
92 : }
93 : }
94 0 : rc = spdk_mem_map_clear_translation(map, (uint64_t)vaddr, size);
95 0 : break;
96 0 : default:
97 0 : SPDK_UNREACHABLE();
98 : }
99 :
100 0 : return rc;
101 : }
102 :
103 : static int
104 0 : rdma_check_contiguous_entries(uint64_t addr_1, uint64_t addr_2)
105 : {
106 : /* Two contiguous mappings will point to the same address which is the start of the RDMA MR. */
107 0 : return addr_1 == addr_2;
108 : }
109 :
110 : const struct spdk_mem_map_ops g_rdma_map_ops = {
111 : .notify_cb = rdma_utils_mem_notify,
112 : .are_contiguous = rdma_check_contiguous_entries
113 : };
114 :
115 : static void
116 0 : _rdma_free_mem_map(struct spdk_rdma_utils_mem_map *map)
117 : {
118 0 : assert(map);
119 :
120 0 : if (map->hooks) {
121 0 : spdk_free(map);
122 : } else {
123 0 : free(map);
124 : }
125 0 : }
126 :
127 : struct spdk_rdma_utils_mem_map *
128 0 : spdk_rdma_utils_create_mem_map(struct ibv_pd *pd, struct spdk_nvme_rdma_hooks *hooks,
129 : uint32_t access_flags)
130 : {
131 : struct spdk_rdma_utils_mem_map *map;
132 :
133 0 : if (pd->context->device->transport_type == IBV_TRANSPORT_IWARP) {
134 : /* IWARP requires REMOTE_WRITE permission for RDMA_READ operation */
135 0 : access_flags |= IBV_ACCESS_REMOTE_WRITE;
136 : }
137 :
138 0 : pthread_mutex_lock(&g_rdma_mr_maps_mutex);
139 : /* Look up existing mem map registration for this pd */
140 0 : LIST_FOREACH(map, &g_rdma_utils_mr_maps, link) {
141 0 : if (map->pd == pd && map->access_flags == access_flags) {
142 0 : map->ref_count++;
143 0 : pthread_mutex_unlock(&g_rdma_mr_maps_mutex);
144 0 : return map;
145 : }
146 : }
147 :
148 0 : if (hooks) {
149 0 : map = spdk_zmalloc(sizeof(*map), 0, NULL, SPDK_ENV_NUMA_ID_ANY, SPDK_MALLOC_DMA);
150 : } else {
151 0 : map = calloc(1, sizeof(*map));
152 : }
153 0 : if (!map) {
154 0 : pthread_mutex_unlock(&g_rdma_mr_maps_mutex);
155 0 : SPDK_ERRLOG("Memory allocation failed\n");
156 0 : return NULL;
157 : }
158 0 : map->pd = pd;
159 0 : map->ref_count = 1;
160 0 : map->hooks = hooks;
161 0 : map->access_flags = access_flags;
162 0 : map->map = spdk_mem_map_alloc(0, &g_rdma_map_ops, map);
163 0 : if (!map->map) {
164 0 : SPDK_ERRLOG("Unable to create memory map\n");
165 0 : _rdma_free_mem_map(map);
166 0 : pthread_mutex_unlock(&g_rdma_mr_maps_mutex);
167 0 : return NULL;
168 : }
169 0 : LIST_INSERT_HEAD(&g_rdma_utils_mr_maps, map, link);
170 :
171 0 : pthread_mutex_unlock(&g_rdma_mr_maps_mutex);
172 :
173 0 : return map;
174 : }
175 :
176 : void
177 0 : spdk_rdma_utils_free_mem_map(struct spdk_rdma_utils_mem_map **_map)
178 : {
179 : struct spdk_rdma_utils_mem_map *map;
180 :
181 0 : if (!_map) {
182 0 : return;
183 : }
184 :
185 0 : map = *_map;
186 0 : if (!map) {
187 0 : return;
188 : }
189 0 : *_map = NULL;
190 :
191 0 : pthread_mutex_lock(&g_rdma_mr_maps_mutex);
192 0 : assert(map->ref_count > 0);
193 0 : map->ref_count--;
194 0 : if (map->ref_count != 0) {
195 0 : pthread_mutex_unlock(&g_rdma_mr_maps_mutex);
196 0 : return;
197 : }
198 :
199 0 : LIST_REMOVE(map, link);
200 0 : pthread_mutex_unlock(&g_rdma_mr_maps_mutex);
201 0 : if (map->map) {
202 0 : spdk_mem_map_free(&map->map);
203 : }
204 0 : _rdma_free_mem_map(map);
205 : }
206 :
207 : int
208 0 : spdk_rdma_utils_get_translation(struct spdk_rdma_utils_mem_map *map, void *address,
209 : size_t length, struct spdk_rdma_utils_memory_translation *translation)
210 : {
211 0 : uint64_t real_length = length;
212 :
213 0 : assert(map);
214 0 : assert(address);
215 0 : assert(translation);
216 :
217 0 : if (map->hooks && map->hooks->get_rkey) {
218 0 : translation->translation_type = SPDK_RDMA_UTILS_TRANSLATION_KEY;
219 0 : translation->mr_or_key.key = spdk_mem_map_translate(map->map, (uint64_t)address, &real_length);
220 : } else {
221 0 : translation->translation_type = SPDK_RDMA_UTILS_TRANSLATION_MR;
222 0 : translation->mr_or_key.mr = (struct ibv_mr *)spdk_mem_map_translate(map->map, (uint64_t)address,
223 : &real_length);
224 0 : if (spdk_unlikely(!translation->mr_or_key.mr)) {
225 0 : SPDK_ERRLOG("No translation for ptr %p, size %zu\n", address, length);
226 0 : return -EINVAL;
227 : }
228 : }
229 :
230 0 : assert(real_length >= length);
231 :
232 0 : return 0;
233 : }
234 :
235 :
236 : static struct rdma_utils_device *
237 3 : rdma_add_dev(struct ibv_context *context)
238 : {
239 : struct rdma_utils_device *dev;
240 :
241 3 : dev = calloc(1, sizeof(*dev));
242 3 : if (dev == NULL) {
243 0 : SPDK_ERRLOG("Failed to allocate RDMA device object.\n");
244 0 : return NULL;
245 : }
246 :
247 3 : dev->pd = ibv_alloc_pd(context);
248 3 : if (dev->pd == NULL) {
249 0 : SPDK_ERRLOG("ibv_alloc_pd() failed: %s (%d)\n", spdk_strerror(errno), errno);
250 0 : free(dev);
251 0 : return NULL;
252 : }
253 :
254 3 : dev->context = context;
255 3 : TAILQ_INSERT_TAIL(&g_dev_list, dev, tailq);
256 :
257 3 : return dev;
258 : }
259 :
260 : static void
261 5 : rdma_remove_dev(struct rdma_utils_device *dev)
262 : {
263 5 : if (!dev->removed || dev->ref > 0) {
264 2 : return;
265 : }
266 :
267 : /* Deallocate protection domain only if the device is already removed and
268 : * there is no reference.
269 : */
270 3 : TAILQ_REMOVE(&g_dev_list, dev, tailq);
271 3 : ibv_dealloc_pd(dev->pd);
272 3 : free(dev);
273 : }
274 :
275 : static int
276 2 : ctx_cmp(const void *_c1, const void *_c2)
277 : {
278 2 : struct ibv_context *c1 = *(struct ibv_context **)_c1;
279 2 : struct ibv_context *c2 = *(struct ibv_context **)_c2;
280 :
281 2 : return c1 < c2 ? -1 : c1 > c2;
282 : }
283 :
284 : static int
285 6 : rdma_sync_dev_list(void)
286 : {
287 : struct ibv_context **new_ctx_list;
288 : int i, j;
289 6 : int num_devs = 0;
290 :
291 : /*
292 : * rdma_get_devices() returns a NULL terminated array of opened RDMA devices,
293 : * and sets num_devs to the number of the returned devices.
294 : */
295 6 : new_ctx_list = rdma_get_devices(&num_devs);
296 6 : if (new_ctx_list == NULL) {
297 0 : SPDK_ERRLOG("rdma_get_devices() failed: %s (%d)\n", spdk_strerror(errno), errno);
298 0 : return -ENODEV;
299 : }
300 :
301 6 : if (num_devs == 0) {
302 0 : rdma_free_devices(new_ctx_list);
303 0 : SPDK_ERRLOG("Returned RDMA device array was empty\n");
304 0 : return -ENODEV;
305 : }
306 :
307 : /*
308 : * Sort new_ctx_list by addresses to update devices easily.
309 : */
310 6 : qsort(new_ctx_list, num_devs, sizeof(struct ibv_context *), ctx_cmp);
311 :
312 6 : if (g_ctx_list == NULL) {
313 : /* If no old array, this is the first call. Add all devices. */
314 3 : for (i = 0; new_ctx_list[i] != NULL; i++) {
315 2 : rdma_add_dev(new_ctx_list[i]);
316 : }
317 :
318 1 : goto exit;
319 : }
320 :
321 13 : for (i = j = 0; new_ctx_list[i] != NULL || g_ctx_list[j] != NULL;) {
322 8 : struct ibv_context *new_ctx = new_ctx_list[i];
323 8 : struct ibv_context *old_ctx = g_ctx_list[j];
324 8 : bool add = false, remove = false;
325 :
326 : /*
327 : * If a context exists only in the new array, create a device for it,
328 : * or if a context exists only in the old array, try removing the
329 : * corresponding device.
330 : */
331 :
332 8 : if (old_ctx == NULL) {
333 0 : add = true;
334 8 : } else if (new_ctx == NULL) {
335 1 : remove = true;
336 7 : } else if (new_ctx < old_ctx) {
337 1 : add = true;
338 6 : } else if (old_ctx < new_ctx) {
339 1 : remove = true;
340 : }
341 :
342 8 : if (add) {
343 1 : rdma_add_dev(new_ctx_list[i]);
344 1 : i++;
345 7 : } else if (remove) {
346 : struct rdma_utils_device *dev, *tmp;
347 :
348 7 : TAILQ_FOREACH_SAFE(dev, &g_dev_list, tailq, tmp) {
349 5 : if (dev->context == g_ctx_list[j]) {
350 2 : dev->removed = true;
351 2 : rdma_remove_dev(dev);
352 : }
353 : }
354 2 : j++;
355 : } else {
356 5 : i++;
357 5 : j++;
358 : }
359 : }
360 :
361 : /* Free the old array. */
362 5 : rdma_free_devices(g_ctx_list);
363 :
364 6 : exit:
365 : /*
366 : * Keep the newly returned array so that allocated protection domains
367 : * are not freed unexpectedly.
368 : */
369 6 : g_ctx_list = new_ctx_list;
370 6 : return 0;
371 : }
372 :
373 : struct ibv_pd *
374 4 : spdk_rdma_utils_get_pd(struct ibv_context *context)
375 : {
376 : struct rdma_utils_device *dev;
377 : int rc;
378 :
379 4 : pthread_mutex_lock(&g_dev_mutex);
380 :
381 4 : rc = rdma_sync_dev_list();
382 4 : if (rc != 0) {
383 0 : pthread_mutex_unlock(&g_dev_mutex);
384 :
385 0 : SPDK_ERRLOG("Failed to sync RDMA device list\n");
386 0 : return NULL;
387 : }
388 :
389 8 : TAILQ_FOREACH(dev, &g_dev_list, tailq) {
390 6 : if (dev->context == context && !dev->removed) {
391 2 : dev->ref++;
392 2 : pthread_mutex_unlock(&g_dev_mutex);
393 :
394 2 : return dev->pd;
395 : }
396 : }
397 :
398 2 : pthread_mutex_unlock(&g_dev_mutex);
399 :
400 2 : SPDK_ERRLOG("Failed to get PD\n");
401 2 : return NULL;
402 : }
403 :
404 : void
405 2 : spdk_rdma_utils_put_pd(struct ibv_pd *pd)
406 : {
407 : struct rdma_utils_device *dev, *tmp;
408 :
409 2 : pthread_mutex_lock(&g_dev_mutex);
410 :
411 5 : TAILQ_FOREACH_SAFE(dev, &g_dev_list, tailq, tmp) {
412 3 : if (dev->pd == pd) {
413 2 : assert(dev->ref > 0);
414 2 : dev->ref--;
415 :
416 2 : rdma_remove_dev(dev);
417 : }
418 : }
419 :
420 2 : rdma_sync_dev_list();
421 :
422 2 : pthread_mutex_unlock(&g_dev_mutex);
423 2 : }
424 :
425 : __attribute__((destructor)) static void
426 2 : _rdma_utils_fini(void)
427 : {
428 : struct rdma_utils_device *dev, *tmp;
429 :
430 3 : TAILQ_FOREACH_SAFE(dev, &g_dev_list, tailq, tmp) {
431 1 : dev->removed = true;
432 1 : dev->ref = 0;
433 1 : rdma_remove_dev(dev);
434 : }
435 :
436 2 : if (g_ctx_list != NULL) {
437 1 : rdma_free_devices(g_ctx_list);
438 1 : g_ctx_list = NULL;
439 : }
440 2 : }
441 :
442 : struct spdk_memory_domain *
443 0 : spdk_rdma_utils_get_memory_domain(struct ibv_pd *pd)
444 : {
445 0 : struct rdma_utils_memory_domain *domain = NULL;
446 0 : struct spdk_memory_domain_ctx ctx = {};
447 : int rc;
448 :
449 0 : pthread_mutex_lock(&g_memory_domains_lock);
450 :
451 0 : TAILQ_FOREACH(domain, &g_memory_domains, link) {
452 0 : if (domain->pd == pd) {
453 0 : domain->ref++;
454 0 : pthread_mutex_unlock(&g_memory_domains_lock);
455 0 : return domain->domain;
456 : }
457 : }
458 :
459 0 : domain = calloc(1, sizeof(*domain));
460 0 : if (!domain) {
461 0 : SPDK_ERRLOG("Memory allocation failed\n");
462 0 : pthread_mutex_unlock(&g_memory_domains_lock);
463 0 : return NULL;
464 : }
465 :
466 0 : domain->rdma_ctx.size = sizeof(domain->rdma_ctx);
467 0 : domain->rdma_ctx.ibv_pd = pd;
468 0 : ctx.size = sizeof(ctx);
469 0 : ctx.user_ctx = &domain->rdma_ctx;
470 0 : ctx.user_ctx_size = domain->rdma_ctx.size;
471 :
472 0 : rc = spdk_memory_domain_create(&domain->domain, SPDK_DMA_DEVICE_TYPE_RDMA, &ctx,
473 : SPDK_RDMA_DMA_DEVICE);
474 0 : if (rc) {
475 0 : SPDK_ERRLOG("Failed to create memory domain\n");
476 0 : free(domain);
477 0 : pthread_mutex_unlock(&g_memory_domains_lock);
478 0 : return NULL;
479 : }
480 :
481 0 : domain->pd = pd;
482 0 : domain->ref = 1;
483 0 : TAILQ_INSERT_TAIL(&g_memory_domains, domain, link);
484 :
485 0 : pthread_mutex_unlock(&g_memory_domains_lock);
486 :
487 0 : return domain->domain;
488 : }
489 :
490 : int
491 0 : spdk_rdma_utils_put_memory_domain(struct spdk_memory_domain *_domain)
492 : {
493 0 : struct rdma_utils_memory_domain *domain = NULL;
494 :
495 0 : if (!_domain) {
496 0 : return 0;
497 : }
498 :
499 0 : pthread_mutex_lock(&g_memory_domains_lock);
500 :
501 0 : TAILQ_FOREACH(domain, &g_memory_domains, link) {
502 0 : if (domain->domain == _domain) {
503 0 : break;
504 : }
505 : }
506 :
507 0 : if (!domain) {
508 0 : pthread_mutex_unlock(&g_memory_domains_lock);
509 0 : return -ENODEV;
510 : }
511 0 : assert(domain->ref > 0);
512 :
513 0 : domain->ref--;
514 :
515 0 : if (domain->ref == 0) {
516 0 : spdk_memory_domain_destroy(domain->domain);
517 0 : TAILQ_REMOVE(&g_memory_domains, domain, link);
518 0 : free(domain);
519 : }
520 :
521 0 : pthread_mutex_unlock(&g_memory_domains_lock);
522 :
523 0 : return 0;
524 : }
525 :
526 : int32_t
527 0 : spdk_rdma_cm_id_get_numa_id(struct rdma_cm_id *cm_id)
528 : {
529 : struct sockaddr *sa;
530 : char addr[64];
531 : char ifc[64];
532 : uint32_t numa_id;
533 : int rc;
534 :
535 0 : sa = rdma_get_local_addr(cm_id);
536 0 : if (sa == NULL) {
537 0 : return SPDK_ENV_NUMA_ID_ANY;
538 : }
539 0 : rc = spdk_net_get_address_string(sa, addr, sizeof(addr));
540 0 : if (rc) {
541 0 : return SPDK_ENV_NUMA_ID_ANY;
542 : }
543 0 : rc = spdk_net_get_interface_name(addr, ifc, sizeof(ifc));
544 0 : if (rc) {
545 0 : return SPDK_ENV_NUMA_ID_ANY;
546 : }
547 0 : rc = spdk_read_sysfs_attribute_uint32(&numa_id,
548 : "/sys/class/net/%s/device/numa_node", ifc);
549 0 : if (rc || numa_id > INT32_MAX) {
550 0 : return SPDK_ENV_NUMA_ID_ANY;
551 : }
552 0 : return (int32_t)numa_id;
553 : }
|