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