| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2017, Microsoft Corporation. | |
| * Copyright (C) 2018, LG Electronics. | |
| * Copyright (c) 2025, Stefan Metzmacher | |
| */ | |
| #include "internal.h" | |
| static int smbdirect_connection_wait_for_rw_credits(struct smbdirect_socket *sc, | |
| int credits) | |
| { | |
| return smbdirect_socket_wait_for_credits(sc, | |
| SMBDIRECT_SOCKET_CONNECTED, | |
| -ENOTCONN, | |
| &sc->rw_io.credits.wait_queue, | |
| &sc->rw_io.credits.count, | |
| credits); | |
| } | |
| static int smbdirect_connection_calc_rw_credits(struct smbdirect_socket *sc, | |
| const void *buf, | |
| size_t len) | |
| { | |
| return DIV_ROUND_UP(smbdirect_get_buf_page_count(buf, len), | |
| sc->rw_io.credits.num_pages); | |
| } | |
| static int smbdirect_connection_rdma_get_sg_list(void *buf, | |
| size_t size, | |
| struct scatterlist *sg_list, | |
| size_t nentries) | |
| { | |
| bool high = is_vmalloc_addr(buf); | |
| struct page *page; | |
| size_t offset, len; | |
| int i = 0; | |
| if (size == 0 || nentries < smbdirect_get_buf_page_count(buf, size)) | |
| return -EINVAL; | |
| offset = offset_in_page(buf); | |
| buf -= offset; | |
| while (size > 0) { | |
| len = min_t(size_t, PAGE_SIZE - offset, size); | |
| if (high) | |
| page = vmalloc_to_page(buf); | |
| else | |
| page = kmap_to_page(buf); | |
| if (!sg_list) | |
| return -EINVAL; | |
| sg_set_page(sg_list, page, len, offset); | |
| sg_list = sg_next(sg_list); | |
| buf += PAGE_SIZE; | |
| size -= len; | |
| offset = 0; | |
| i++; | |
| } | |
| return i; | |
| } | |
| static void smbdirect_connection_rw_io_free(struct smbdirect_rw_io *msg, | |
| enum dma_data_direction dir) | |
| { | |
| struct smbdirect_socket *sc = msg->socket; | |
| rdma_rw_ctx_destroy(&msg->rdma_ctx, | |
| sc->ib.qp, | |
| sc->ib.qp->port, | |
| msg->sgt.sgl, | |
| msg->sgt.nents, | |
| dir); | |
| sg_free_table_chained(&msg->sgt, SG_CHUNK_SIZE); | |
| kfree(msg); | |
| } | |
| static void smbdirect_connection_rdma_rw_done(struct ib_cq *cq, struct ib_wc *wc, | |
| enum dma_data_direction dir) | |
| { | |
| struct smbdirect_rw_io *msg = | |
| container_of(wc->wr_cqe, struct smbdirect_rw_io, cqe); | |
| struct smbdirect_socket *sc = msg->socket; | |
| if (wc->status != IB_WC_SUCCESS) { | |
| msg->error = -EIO; | |
| pr_err("read/write error. opcode = %d, status = %s(%d)\n", | |
| wc->opcode, ib_wc_status_msg(wc->status), wc->status); | |
| if (wc->status != IB_WC_WR_FLUSH_ERR) | |
| smbdirect_socket_schedule_cleanup(sc, msg->error); | |
| } | |
| complete(msg->completion); | |
| } | |
| static void smbdirect_connection_rdma_read_done(struct ib_cq *cq, struct ib_wc *wc) | |
| { | |
| smbdirect_connection_rdma_rw_done(cq, wc, DMA_FROM_DEVICE); | |
| } | |
| static void smbdirect_connection_rdma_write_done(struct ib_cq *cq, struct ib_wc *wc) | |
| { | |
| smbdirect_connection_rdma_rw_done(cq, wc, DMA_TO_DEVICE); | |
| } | |
| int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc, | |
| void *buf, size_t buf_len, | |
| struct smbdirect_buffer_descriptor_v1 *desc, | |
| size_t desc_len, | |
| bool is_read) | |
| { | |
| const struct smbdirect_socket_parameters *sp = &sc->parameters; | |
| enum dma_data_direction direction = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; | |
| struct smbdirect_rw_io *msg, *next_msg; | |
| size_t i; | |
| int ret; | |
| DECLARE_COMPLETION_ONSTACK(completion); | |
| struct ib_send_wr *first_wr; | |
| LIST_HEAD(msg_list); | |
| u8 *desc_buf; | |
| int credits_needed; | |
| size_t desc_buf_len, desc_num = 0; | |
| if (sc->status != SMBDIRECT_SOCKET_CONNECTED) | |
| return -ENOTCONN; | |
| if (buf_len > sp->max_read_write_size) | |
| return -EINVAL; | |
| /* calculate needed credits */ | |
| credits_needed = 0; | |
| desc_buf = buf; | |
| for (i = 0; i < desc_len / sizeof(*desc); i++) { | |
| if (!buf_len) | |
| break; | |
| desc_buf_len = le32_to_cpu(desc[i].length); | |
| if (!desc_buf_len) | |
| return -EINVAL; | |
| if (desc_buf_len > buf_len) { | |
| desc_buf_len = buf_len; | |
| desc[i].length = cpu_to_le32(desc_buf_len); | |
| } | |
| credits_needed += smbdirect_connection_calc_rw_credits(sc, | |
| desc_buf, | |
| desc_buf_len); | |
| desc_buf += desc_buf_len; | |
| buf_len -= desc_buf_len; | |
| desc_num++; | |
| } | |
| smbdirect_log_rdma_rw(sc, SMBDIRECT_LOG_INFO, | |
| "RDMA %s, len %zu, needed credits %d\n", | |
| str_read_write(is_read), buf_len, credits_needed); | |
| ret = smbdirect_connection_wait_for_rw_credits(sc, credits_needed); | |
| if (ret < 0) | |
| return ret; | |
| /* build rdma_rw_ctx for each descriptor */ | |
| desc_buf = buf; | |
| for (i = 0; i < desc_num; i++) { | |
| size_t page_count; | |
| msg = kzalloc_flex(*msg, sg_list, SG_CHUNK_SIZE, | |
| sc->rw_io.mem.gfp_mask); | |
| if (!msg) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| desc_buf_len = le32_to_cpu(desc[i].length); | |
| page_count = smbdirect_get_buf_page_count(desc_buf, desc_buf_len); | |
| msg->socket = sc; | |
| msg->cqe.done = is_read ? | |
| smbdirect_connection_rdma_read_done : | |
| smbdirect_connection_rdma_write_done; | |
| msg->completion = &completion; | |
| msg->sgt.sgl = &msg->sg_list[0]; | |
| ret = sg_alloc_table_chained(&msg->sgt, | |
| page_count, | |
| msg->sg_list, | |
| SG_CHUNK_SIZE); | |
| if (ret) { | |
| ret = -ENOMEM; | |
| goto free_msg; | |
| } | |
| ret = smbdirect_connection_rdma_get_sg_list(desc_buf, | |
| desc_buf_len, | |
| msg->sgt.sgl, | |
| msg->sgt.orig_nents); | |
| if (ret < 0) | |
| goto free_table; | |
| ret = rdma_rw_ctx_init(&msg->rdma_ctx, | |
| sc->ib.qp, | |
| sc->ib.qp->port, | |
| msg->sgt.sgl, | |
| page_count, | |
| 0, | |
| le64_to_cpu(desc[i].offset), | |
| le32_to_cpu(desc[i].token), | |
| direction); | |
| if (ret < 0) { | |
| pr_err("failed to init rdma_rw_ctx: %d\n", ret); | |
| goto free_table; | |
| } | |
| list_add_tail(&msg->list, &msg_list); | |
| desc_buf += desc_buf_len; | |
| } | |
| /* concatenate work requests of rdma_rw_ctxs */ | |
| first_wr = NULL; | |
| list_for_each_entry_reverse(msg, &msg_list, list) { | |
| first_wr = rdma_rw_ctx_wrs(&msg->rdma_ctx, | |
| sc->ib.qp, | |
| sc->ib.qp->port, | |
| &msg->cqe, | |
| first_wr); | |
| } | |
| ret = ib_post_send(sc->ib.qp, first_wr, NULL); | |
| if (ret) { | |
| pr_err("failed to post send wr for RDMA R/W: %d\n", ret); | |
| goto out; | |
| } | |
| msg = list_last_entry(&msg_list, struct smbdirect_rw_io, list); | |
| wait_for_completion(&completion); | |
| ret = msg->error; | |
| out: | |
| list_for_each_entry_safe(msg, next_msg, &msg_list, list) { | |
| list_del(&msg->list); | |
| smbdirect_connection_rw_io_free(msg, direction); | |
| } | |
| atomic_add(credits_needed, &sc->rw_io.credits.count); | |
| wake_up(&sc->rw_io.credits.wait_queue); | |
| return ret; | |
| free_table: | |
| sg_free_table_chained(&msg->sgt, SG_CHUNK_SIZE); | |
| free_msg: | |
| kfree(msg); | |
| goto out; | |
| } | |
| EXPORT_SYMBOL_GPL(smbdirect_connection_rdma_xmit); |