mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-27 16:49:22 -04:00
This is a better solution than EXPORT_SYMBOL_FOR_MODULES(__sym, "cifs,ksmbd") as it makes it possible to rebuild smbdirect.ko against a running kernel and then load the existing cifs.ko and ksmbd.ko from the running kernel. Suggested-by: Christoph Hellwig <hch@infradead.org> Link: https://lore.kernel.org/linux-cifs/aehrPuY60VMcYGU8@infradead.org/ Cc: Steve French <smfrench@gmail.com> Cc: Tom Talpey <tom@talpey.com> Cc: Long Li <longli@microsoft.com> Cc: Namjae Jeon <linkinjeon@kernel.org> Cc: Christoph Hellwig <hch@infradead.org> Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher <metze@samba.org> Signed-off-by: Steve French <stfrench@microsoft.com>
256 lines
6.2 KiB
C
256 lines
6.2 KiB
C
// 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);
|
|
buf_len = 0;
|
|
}
|
|
|
|
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);
|