| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2017, Microsoft Corporation. | |
| * Copyright (c) 2025, Stefan Metzmacher | |
| */ | |
| #include "internal.h" | |
| /* | |
| * Allocate MRs used for RDMA read/write | |
| * The number of MRs will not exceed hardware capability in responder_resources | |
| * All MRs are kept in mr_list. The MR can be recovered after it's used | |
| * Recovery is done in smbd_mr_recovery_work. The content of list entry changes | |
| * as MRs are used and recovered for I/O, but the list links will not change | |
| */ | |
| int smbdirect_connection_create_mr_list(struct smbdirect_socket *sc) | |
| { | |
| const struct smbdirect_socket_parameters *sp = &sc->parameters; | |
| struct smbdirect_mr_io *mr; | |
| int ret; | |
| u32 i; | |
| if (sp->responder_resources == 0) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "responder_resources negotiated as 0\n"); | |
| return -EINVAL; | |
| } | |
| /* Allocate more MRs (2x) than hardware responder_resources */ | |
| for (i = 0; i < sp->responder_resources * 2; i++) { | |
| mr = kzalloc_obj(*mr); | |
| if (!mr) { | |
| ret = -ENOMEM; | |
| goto kzalloc_mr_failed; | |
| } | |
| kref_init(&mr->kref); | |
| mutex_init(&mr->mutex); | |
| mr->mr = ib_alloc_mr(sc->ib.pd, | |
| sc->mr_io.type, | |
| sp->max_frmr_depth); | |
| if (IS_ERR(mr->mr)) { | |
| ret = PTR_ERR(mr->mr); | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "ib_alloc_mr failed ret=%d (%1pe) type=0x%x max_frmr_depth=%u\n", | |
| ret, SMBDIRECT_DEBUG_ERR_PTR(ret), | |
| sc->mr_io.type, sp->max_frmr_depth); | |
| goto ib_alloc_mr_failed; | |
| } | |
| mr->sgt.sgl = kzalloc_objs(struct scatterlist, sp->max_frmr_depth); | |
| if (!mr->sgt.sgl) { | |
| ret = -ENOMEM; | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "failed to allocate sgl, max_frmr_depth=%u\n", | |
| sp->max_frmr_depth); | |
| goto kcalloc_sgl_failed; | |
| } | |
| mr->state = SMBDIRECT_MR_READY; | |
| mr->socket = sc; | |
| list_add_tail(&mr->list, &sc->mr_io.all.list); | |
| atomic_inc(&sc->mr_io.ready.count); | |
| } | |
| return 0; | |
| kcalloc_sgl_failed: | |
| ib_dereg_mr(mr->mr); | |
| ib_alloc_mr_failed: | |
| mutex_destroy(&mr->mutex); | |
| kfree(mr); | |
| kzalloc_mr_failed: | |
| smbdirect_connection_destroy_mr_list(sc); | |
| return ret; | |
| } | |
| static void smbdirect_mr_io_disable_locked(struct smbdirect_mr_io *mr) | |
| { | |
| struct smbdirect_socket *sc = mr->socket; | |
| lockdep_assert_held(&mr->mutex); | |
| if (mr->state == SMBDIRECT_MR_DISABLED) | |
| return; | |
| if (mr->mr) | |
| ib_dereg_mr(mr->mr); | |
| if (mr->sgt.nents) | |
| ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir); | |
| kfree(mr->sgt.sgl); | |
| mr->mr = NULL; | |
| mr->sgt.sgl = NULL; | |
| mr->sgt.nents = 0; | |
| mr->state = SMBDIRECT_MR_DISABLED; | |
| } | |
| static void smbdirect_mr_io_free_locked(struct kref *kref) | |
| { | |
| struct smbdirect_mr_io *mr = | |
| container_of(kref, struct smbdirect_mr_io, kref); | |
| lockdep_assert_held(&mr->mutex); | |
| /* | |
| * smbdirect_mr_io_disable_locked() should already be called! | |
| */ | |
| if (WARN_ON_ONCE(mr->state != SMBDIRECT_MR_DISABLED)) | |
| smbdirect_mr_io_disable_locked(mr); | |
| mutex_unlock(&mr->mutex); | |
| mutex_destroy(&mr->mutex); | |
| kfree(mr); | |
| } | |
| void smbdirect_connection_destroy_mr_list(struct smbdirect_socket *sc) | |
| { | |
| struct smbdirect_mr_io *mr, *tmp; | |
| LIST_HEAD(all_list); | |
| unsigned long flags; | |
| spin_lock_irqsave(&sc->mr_io.all.lock, flags); | |
| list_splice_tail_init(&sc->mr_io.all.list, &all_list); | |
| spin_unlock_irqrestore(&sc->mr_io.all.lock, flags); | |
| list_for_each_entry_safe(mr, tmp, &all_list, list) { | |
| mutex_lock(&mr->mutex); | |
| smbdirect_mr_io_disable_locked(mr); | |
| list_del(&mr->list); | |
| mr->socket = NULL; | |
| /* | |
| * No kref_put_mutex() as it's already locked. | |
| * | |
| * If smbdirect_mr_io_free_locked() is called | |
| * and the mutex is unlocked and mr is gone, | |
| * in that case kref_put() returned 1. | |
| * | |
| * If kref_put() returned 0 we know that | |
| * smbdirect_mr_io_free_locked() didn't | |
| * run. Not by us nor by anyone else, as we | |
| * still hold the mutex, so we need to unlock. | |
| * | |
| * If the mr is still registered it will | |
| * be dangling (detached from the connection | |
| * waiting for smbd_deregister_mr() to be | |
| * called in order to free the memory. | |
| */ | |
| if (!kref_put(&mr->kref, smbdirect_mr_io_free_locked)) | |
| mutex_unlock(&mr->mutex); | |
| } | |
| } | |
| /* | |
| * Get a MR from mr_list. This function waits until there is at least one MR | |
| * available in the list. There may be several CPUs issuing I/O trying to get MR | |
| * at the same time, mr_list_lock is used to protect this situation. | |
| */ | |
| static struct smbdirect_mr_io * | |
| smbdirect_connection_get_mr_io(struct smbdirect_socket *sc) | |
| { | |
| struct smbdirect_mr_io *mr; | |
| unsigned long flags; | |
| int ret; | |
| again: | |
| ret = wait_event_interruptible(sc->mr_io.ready.wait_queue, | |
| atomic_read(&sc->mr_io.ready.count) || | |
| sc->status != SMBDIRECT_SOCKET_CONNECTED); | |
| if (ret) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "wait_event_interruptible ret=%d (%1pe)\n", | |
| ret, SMBDIRECT_DEBUG_ERR_PTR(ret)); | |
| return NULL; | |
| } | |
| if (sc->status != SMBDIRECT_SOCKET_CONNECTED) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "sc->status=%s sc->first_error=%1pe\n", | |
| smbdirect_socket_status_string(sc->status), | |
| SMBDIRECT_DEBUG_ERR_PTR(sc->first_error)); | |
| return NULL; | |
| } | |
| spin_lock_irqsave(&sc->mr_io.all.lock, flags); | |
| list_for_each_entry(mr, &sc->mr_io.all.list, list) { | |
| if (mr->state == SMBDIRECT_MR_READY) { | |
| mr->state = SMBDIRECT_MR_REGISTERED; | |
| kref_get(&mr->kref); | |
| spin_unlock_irqrestore(&sc->mr_io.all.lock, flags); | |
| atomic_dec(&sc->mr_io.ready.count); | |
| atomic_inc(&sc->mr_io.used.count); | |
| return mr; | |
| } | |
| } | |
| spin_unlock_irqrestore(&sc->mr_io.all.lock, flags); | |
| /* | |
| * It is possible that we could fail to get MR because other processes may | |
| * try to acquire a MR at the same time. If this is the case, retry it. | |
| */ | |
| goto again; | |
| } | |
| static void smbdirect_connection_mr_io_register_done(struct ib_cq *cq, struct ib_wc *wc) | |
| { | |
| struct smbdirect_mr_io *mr = | |
| container_of(wc->wr_cqe, struct smbdirect_mr_io, cqe); | |
| struct smbdirect_socket *sc = mr->socket; | |
| if (wc->status != IB_WC_SUCCESS) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "wc->status=%s opcode=%d\n", | |
| ib_wc_status_msg(wc->status), wc->opcode); | |
| smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED); | |
| } | |
| } | |
| static void smbdirect_connection_mr_io_local_inv_done(struct ib_cq *cq, struct ib_wc *wc) | |
| { | |
| struct smbdirect_mr_io *mr = | |
| container_of(wc->wr_cqe, struct smbdirect_mr_io, cqe); | |
| struct smbdirect_socket *sc = mr->socket; | |
| mr->state = SMBDIRECT_MR_INVALIDATED; | |
| if (wc->status != IB_WC_SUCCESS) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "invalidate failed status=%s\n", | |
| ib_wc_status_msg(wc->status)); | |
| smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED); | |
| } | |
| complete(&mr->invalidate_done); | |
| } | |
| /* | |
| * Transcribe the pages from an iterator into an MR scatterlist. | |
| */ | |
| static int smbdirect_iter_to_sgt(struct iov_iter *iter, | |
| struct sg_table *sgt, | |
| unsigned int max_sg) | |
| { | |
| int ret; | |
| memset(sgt->sgl, 0, max_sg * sizeof(struct scatterlist)); | |
| ret = extract_iter_to_sg(iter, iov_iter_count(iter), sgt, max_sg, 0); | |
| WARN_ON(ret < 0); | |
| if (sgt->nents > 0) | |
| sg_mark_end(&sgt->sgl[sgt->nents - 1]); | |
| return ret; | |
| } | |
| /* | |
| * Register memory for RDMA read/write | |
| * iter: the buffer to register memory with | |
| * writing: true if this is a RDMA write (SMB read), false for RDMA read | |
| * need_invalidate: true if this MR needs to be locally invalidated after I/O | |
| * return value: the MR registered, NULL if failed. | |
| */ | |
| struct smbdirect_mr_io * | |
| smbdirect_connection_register_mr_io(struct smbdirect_socket *sc, | |
| struct iov_iter *iter, | |
| bool writing, | |
| bool need_invalidate) | |
| { | |
| const struct smbdirect_socket_parameters *sp = &sc->parameters; | |
| struct smbdirect_mr_io *mr; | |
| int ret, num_pages, num_mapped; | |
| struct ib_reg_wr *reg_wr; | |
| num_pages = iov_iter_npages(iter, sp->max_frmr_depth + 1); | |
| if (num_pages > sp->max_frmr_depth) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "num_pages=%d max_frmr_depth=%d\n", | |
| num_pages, sp->max_frmr_depth); | |
| WARN_ON_ONCE(1); | |
| return NULL; | |
| } | |
| mr = smbdirect_connection_get_mr_io(sc); | |
| if (!mr) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "smbdirect_connection_get_mr_io returning NULL\n"); | |
| return NULL; | |
| } | |
| mutex_lock(&mr->mutex); | |
| mr->dir = writing ? DMA_FROM_DEVICE : DMA_TO_DEVICE; | |
| mr->need_invalidate = need_invalidate; | |
| mr->sgt.nents = 0; | |
| mr->sgt.orig_nents = 0; | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_INFO, | |
| "num_pages=%u count=%zu depth=%u\n", | |
| num_pages, iov_iter_count(iter), sp->max_frmr_depth); | |
| smbdirect_iter_to_sgt(iter, &mr->sgt, sp->max_frmr_depth); | |
| num_mapped = ib_dma_map_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir); | |
| if (!num_mapped) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "ib_dma_map_sg num_pages=%u dir=%x num_mapped=%d\n", | |
| num_pages, mr->dir, num_mapped); | |
| ret = -EIO; | |
| goto dma_map_error; | |
| } | |
| ret = ib_map_mr_sg(mr->mr, mr->sgt.sgl, num_mapped, NULL, PAGE_SIZE); | |
| if (ret != num_mapped) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "ib_map_mr_sg failed ret = %d num_mapped = %u\n", | |
| ret, num_mapped); | |
| if (ret >= 0) | |
| ret = -EIO; | |
| goto map_mr_error; | |
| } | |
| ib_update_fast_reg_key(mr->mr, ib_inc_rkey(mr->mr->rkey)); | |
| reg_wr = &mr->wr; | |
| reg_wr->wr.opcode = IB_WR_REG_MR; | |
| mr->cqe.done = smbdirect_connection_mr_io_register_done; | |
| reg_wr->wr.wr_cqe = &mr->cqe; | |
| reg_wr->wr.num_sge = 0; | |
| reg_wr->wr.send_flags = IB_SEND_SIGNALED; | |
| reg_wr->mr = mr->mr; | |
| reg_wr->key = mr->mr->rkey; | |
| reg_wr->access = writing ? | |
| IB_ACCESS_REMOTE_WRITE | IB_ACCESS_LOCAL_WRITE : | |
| IB_ACCESS_REMOTE_READ; | |
| /* | |
| * There is no need for waiting for complemtion on ib_post_send | |
| * on IB_WR_REG_MR. Hardware enforces a barrier and order of execution | |
| * on the next ib_post_send when we actually send I/O to remote peer | |
| */ | |
| ret = ib_post_send(sc->ib.qp, ®_wr->wr, NULL); | |
| if (!ret) { | |
| /* | |
| * smbdirect_connection_get_mr_io() gave us a reference | |
| * via kref_get(&mr->kref), we keep that and let | |
| * the caller use smbdirect_connection_deregister_mr_io() | |
| * to remove it again. | |
| */ | |
| mutex_unlock(&mr->mutex); | |
| return mr; | |
| } | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "ib_post_send failed ret=%d (%1pe) reg_wr->key=0x%x\n", | |
| ret, SMBDIRECT_DEBUG_ERR_PTR(ret), reg_wr->key); | |
| map_mr_error: | |
| ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir); | |
| dma_map_error: | |
| mr->sgt.nents = 0; | |
| mr->state = SMBDIRECT_MR_ERROR; | |
| atomic_dec(&sc->mr_io.used.count); | |
| smbdirect_socket_schedule_cleanup(sc, ret); | |
| /* | |
| * smbdirect_connection_get_mr_io() gave us a reference | |
| * via kref_get(&mr->kref), we need to remove it again | |
| * on error. | |
| * | |
| * No kref_put_mutex() as it's already locked. | |
| * | |
| * If smbdirect_mr_io_free_locked() is called | |
| * and the mutex is unlocked and mr is gone, | |
| * in that case kref_put() returned 1. | |
| * | |
| * If kref_put() returned 0 we know that | |
| * smbdirect_mr_io_free_locked() didn't | |
| * run. Not by us nor by anyone else, as we | |
| * still hold the mutex, so we need to unlock. | |
| */ | |
| if (!kref_put(&mr->kref, smbdirect_mr_io_free_locked)) | |
| mutex_unlock(&mr->mutex); | |
| return NULL; | |
| } | |
| EXPORT_SYMBOL_GPL(smbdirect_connection_register_mr_io); | |
| void smbdirect_mr_io_fill_buffer_descriptor(struct smbdirect_mr_io *mr, | |
| struct smbdirect_buffer_descriptor_v1 *v1) | |
| { | |
| mutex_lock(&mr->mutex); | |
| if (mr->state == SMBDIRECT_MR_REGISTERED) { | |
| v1->offset = cpu_to_le64(mr->mr->iova); | |
| v1->token = cpu_to_le32(mr->mr->rkey); | |
| v1->length = cpu_to_le32(mr->mr->length); | |
| } else { | |
| v1->offset = cpu_to_le64(U64_MAX); | |
| v1->token = cpu_to_le32(U32_MAX); | |
| v1->length = cpu_to_le32(U32_MAX); | |
| } | |
| mutex_unlock(&mr->mutex); | |
| } | |
| EXPORT_SYMBOL_GPL(smbdirect_mr_io_fill_buffer_descriptor); | |
| /* | |
| * Deregister a MR after I/O is done | |
| * This function may wait if remote invalidation is not used | |
| * and we have to locally invalidate the buffer to prevent data is being | |
| * modified by remote peer after upper layer consumes it | |
| */ | |
| void smbdirect_connection_deregister_mr_io(struct smbdirect_mr_io *mr) | |
| { | |
| struct smbdirect_socket *sc = mr->socket; | |
| int ret = 0; | |
| lock_again: | |
| mutex_lock(&mr->mutex); | |
| if (mr->state == SMBDIRECT_MR_DISABLED) | |
| goto put_kref; | |
| if (sc->status != SMBDIRECT_SOCKET_CONNECTED) { | |
| smbdirect_mr_io_disable_locked(mr); | |
| goto put_kref; | |
| } | |
| if (mr->need_invalidate) { | |
| struct ib_send_wr *wr = &mr->inv_wr; | |
| /* Need to finish local invalidation before returning */ | |
| wr->opcode = IB_WR_LOCAL_INV; | |
| mr->cqe.done = smbdirect_connection_mr_io_local_inv_done; | |
| wr->wr_cqe = &mr->cqe; | |
| wr->num_sge = 0; | |
| wr->ex.invalidate_rkey = mr->mr->rkey; | |
| wr->send_flags = IB_SEND_SIGNALED; | |
| init_completion(&mr->invalidate_done); | |
| ret = ib_post_send(sc->ib.qp, wr, NULL); | |
| if (ret) { | |
| smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR, | |
| "ib_post_send failed ret=%d (%1pe)\n", | |
| ret, SMBDIRECT_DEBUG_ERR_PTR(ret)); | |
| smbdirect_mr_io_disable_locked(mr); | |
| smbdirect_socket_schedule_cleanup(sc, ret); | |
| goto done; | |
| } | |
| /* | |
| * We still hold the reference to mr | |
| * so we can unlock while waiting. | |
| */ | |
| mutex_unlock(&mr->mutex); | |
| wait_for_completion(&mr->invalidate_done); | |
| mr->need_invalidate = false; | |
| goto lock_again; | |
| } else | |
| /* | |
| * For remote invalidation, just set it to SMBDIRECT_MR_INVALIDATED | |
| * and defer to mr_recovery_work to recover the MR for next use | |
| */ | |
| mr->state = SMBDIRECT_MR_INVALIDATED; | |
| if (mr->sgt.nents) { | |
| ib_dma_unmap_sg(sc->ib.dev, mr->sgt.sgl, mr->sgt.nents, mr->dir); | |
| mr->sgt.nents = 0; | |
| } | |
| WARN_ONCE(mr->state != SMBDIRECT_MR_INVALIDATED, | |
| "mr->state[%u] != SMBDIRECT_MR_INVALIDATED[%u]\n", | |
| mr->state, SMBDIRECT_MR_INVALIDATED); | |
| mr->state = SMBDIRECT_MR_READY; | |
| if (atomic_inc_return(&sc->mr_io.ready.count) == 1) | |
| wake_up(&sc->mr_io.ready.wait_queue); | |
| done: | |
| atomic_dec(&sc->mr_io.used.count); | |
| put_kref: | |
| /* | |
| * No kref_put_mutex() as it's already locked. | |
| * | |
| * If smbdirect_mr_io_free_locked() is called | |
| * and the mutex is unlocked and mr is gone, | |
| * in that case kref_put() returned 1. | |
| * | |
| * If kref_put() returned 0 we know that | |
| * smbdirect_mr_io_free_locked() didn't | |
| * run. Not by us nor by anyone else, as we | |
| * still hold the mutex, so we need to unlock | |
| * and keep the mr in SMBDIRECT_MR_READY or | |
| * SMBDIRECT_MR_ERROR state. | |
| */ | |
| if (!kref_put(&mr->kref, smbdirect_mr_io_free_locked)) | |
| mutex_unlock(&mr->mutex); | |
| } | |
| EXPORT_SYMBOL_GPL(smbdirect_connection_deregister_mr_io); |