From: Huiwen He <huiwen.he@linux.dev>
To: linkinjeon@kernel.org
Cc: linux-cifs@vger.kernel.org, senozhatsky@chromium.org,
tom@talpey.com, chenxiaosong@chenxiaosong.com, tj@kernel.org,
linux-kernel@vger.kernel.org
Subject: [RFC PATCH 4/4] ksmbd: send READ payloads with MSG_SPLICE_PAGES
Date: Thu, 22 Oct 2026 17:25:14 +0800 [thread overview]
Message-ID: <20261022092514.2590010-5-huiwen.he@linux.dev> (raw)
In-Reply-To: <20261022092514.2590010-1-huiwen.he@linux.dev>
From: Huiwen He <hehuiwen@kylinos.cn>
The TCP send path copies READ payloads that ksmbd has already read into
its response buffer, adding CPU and memory bandwidth overhead.
Use page-backed READ buffers and MSG_SPLICE_PAGES to avoid this copy.
Keep the existing copy path as a fallback when page-backed sending is
not applicable.
Tested with fio on a 128-CPU server with a local 4-vCPU client VM
(tmpfs, 16 jobs, QD32 per job, libaio, direct=0, cache=none;
mixed=50/50 R/W). Throughput (MiB/s) and paired changes are medians
of four runs.
A: read_splice=0; B: read_splice=1.
Block Ch Workload A MiB/s B MiB/s B/A change
----- -- --------- --------- --------- ----------
4KiB 1 READ 290.73 282.21 -2.94%
4KiB 1 WRITE 193.01 192.84 +1.17%
4KiB 1 mixed 238.67 240.15 +0.55%
4KiB 2 READ 404.46 404.66 +1.17%
4KiB 2 WRITE 334.67 342.19 +0.34%
4KiB 2 mixed 338.24 336.34 -0.38%
4KiB 4 READ 453.28 456.10 -0.87%
4KiB 4 WRITE 415.69 417.24 +0.37%
4KiB 4 mixed 396.75 386.93 -1.41%
64KiB 1 READ 2162.74 2558.10 +19.56%
64KiB 1 WRITE 1976.37 2031.92 +1.26%
64KiB 1 mixed 1974.08 2176.16 +10.13%
64KiB 2 READ 3246.35 3591.95 +10.58%
64KiB 2 WRITE 3222.85 3205.70 -0.66%
64KiB 2 mixed 3283.75 3317.22 +0.86%
64KiB 4 READ 4227.13 4427.29 +5.01%
64KiB 4 WRITE 3089.17 3047.30 -1.36%
64KiB 4 mixed 3877.03 3962.34 +2.09%
1MiB 1 READ 2126.23 3353.03 +59.40%
1MiB 1 WRITE 3452.84 3460.33 +0.25%
1MiB 1 mixed 2961.50 3096.88 +4.64%
1MiB 2 READ 3887.33 5546.88 +42.10%
1MiB 2 WRITE 4158.22 4174.43 +0.18%
1MiB 2 mixed 4335.01 4954.98 +18.71%
1MiB 4 READ 6707.91 8311.51 +23.28%
1MiB 4 WRITE 3557.26 3612.88 +0.38%
1MiB 4 mixed 5444.00 5604.89 +3.12%
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
---
fs/smb/server/connection.c | 1 +
fs/smb/server/connection.h | 2 +
fs/smb/server/ksmbd_work.c | 88 ++++++++++++++++++-------
fs/smb/server/ksmbd_work.h | 9 +++
fs/smb/server/server.c | 1 +
fs/smb/server/smb2pdu.c | 120 ++++++++++++++++++++++++++++------
fs/smb/server/smb2pdu.h | 1 +
fs/smb/server/transport_tcp.c | 61 +++++++++++++++--
8 files changed, 235 insertions(+), 48 deletions(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 4cb92d6599ee..4eb59d81f966 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -518,6 +518,7 @@ static int __ksmbd_conn_write(struct ksmbd_work *work,
tx->iov = work->iov;
tx->iov_cnt = work->iov_cnt;
tx->size = get_rfc1002_len(work->iov[0].iov_base) + 4;
+ tx->splice_header_len = work->splice_header_len;
tx->need_invalidate_rkey = work->need_invalidate_rkey;
tx->remote_key = work->remote_key;
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index a92e0fea0f06..27a8b17bcbb7 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -166,6 +166,8 @@ struct ksmbd_transport_write {
struct kvec *iov;
int iov_cnt;
int size;
+ /* Prefix to copy into a page fragment before the page-backed last iov. */
+ unsigned int splice_header_len;
bool need_invalidate_rkey;
unsigned int remote_key;
int msg_flags;
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index e1b87bc0c949..9bdd9af1b955 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -35,6 +35,47 @@ bool ksmbd_queue_request(struct ksmbd_work *work, bool use_write_wq)
return queue_work(wq, &work->work);
}
+struct aux_read *ksmbd_alloc_read_buf(size_t length, bool allow_splice)
+{
+ struct aux_read *ar;
+ size_t alloc_size = ALIGN(length, 8);
+
+ ar = kzalloc_obj(*ar, KSMBD_DEFAULT_GFP);
+ if (!ar)
+ return NULL;
+
+ if (allow_splice && length) {
+ unsigned int order = get_order(alloc_size);
+
+ /* TCP references to tails must keep the whole allocation alive. */
+ if (order <= MAX_PAGE_ORDER)
+ ar->page = alloc_pages(GFP_KERNEL | __GFP_COMP |
+ __GFP_NORETRY | __GFP_NOWARN, order);
+ }
+ if (ar->page)
+ ar->buf = page_address(ar->page);
+ else
+ ar->buf = kvmalloc(alloc_size, KSMBD_DEFAULT_GFP);
+ if (!ar->buf) {
+ kfree(ar);
+ return NULL;
+ }
+ return ar;
+}
+
+void ksmbd_free_read_buf(struct aux_read *ar)
+{
+ if (!ar)
+ return;
+ if (ar->page) {
+ /* TCP may still own references; never reuse or modify these pages. */
+ put_page(ar->page);
+ } else {
+ kvfree(ar->buf);
+ }
+ kfree(ar);
+}
+
static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
{
struct kvec *new;
@@ -92,9 +133,8 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
kvfree(work->response_buf);
list_for_each_entry_safe(ar, tmp, &work->aux_read_list, entry) {
- kvfree(ar->buf);
list_del(&ar->entry);
- kfree(ar);
+ ksmbd_free_read_buf(ar);
}
kfree(work->tr_buf);
@@ -188,23 +228,14 @@ static inline void __ksmbd_iov_pin(struct ksmbd_work *work, void *ib,
work->iov_cnt++;
}
-static int __ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len,
- void *aux_buf, unsigned int aux_size)
+/* On success, work owns ar, including a buffer with no response payload. */
+int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
+ struct aux_read *ar, unsigned int aux_size)
{
- struct aux_read *ar = NULL;
- int need_iov_cnt = 1;
-
- if (aux_size) {
- need_iov_cnt++;
- ar = kmalloc_obj(struct aux_read, KSMBD_DEFAULT_GFP);
- if (!ar)
- return -ENOMEM;
- }
+ int need_iov_cnt = aux_size ? 2 : 1;
- if (ksmbd_reserve_iov(work, need_iov_cnt)) {
- kfree(ar);
+ if (ksmbd_reserve_iov(work, need_iov_cnt))
return -ENOMEM;
- }
/* Plus rfc_length size on first iov */
if (!work->iov_idx) {
@@ -218,25 +249,36 @@ static int __ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len,
inc_rfc1001_len(work->iov[0].iov_base, len);
if (aux_size) {
- __ksmbd_iov_pin(work, aux_buf, aux_size);
+ __ksmbd_iov_pin(work, ar->buf, aux_size);
inc_rfc1001_len(work->iov[0].iov_base, aux_size);
-
- ar->buf = aux_buf;
- list_add(&ar->entry, &work->aux_read_list);
}
+ if (ar)
+ list_add(&ar->entry, &work->aux_read_list);
return 0;
}
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len)
{
- return __ksmbd_iov_pin_rsp(work, ib, len, NULL, 0);
+ return ksmbd_iov_pin_rsp_read(work, ib, len, NULL, 0);
}
-int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
+int ksmbd_iov_pin_rsp_data(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size)
{
- return __ksmbd_iov_pin_rsp(work, ib, len, aux_buf, aux_size);
+ struct aux_read *ar;
+ int ret;
+
+ if (!aux_size)
+ return ksmbd_iov_pin_rsp(work, ib, len);
+ ar = kzalloc_obj(*ar, KSMBD_DEFAULT_GFP);
+ if (!ar)
+ return -ENOMEM;
+ ar->buf = aux_buf;
+ ret = ksmbd_iov_pin_rsp_read(work, ib, len, ar, aux_size);
+ if (ret)
+ kfree(ar); /* The caller still owns aux_buf. */
+ return ret;
}
int allocate_interim_rsp_buf(struct ksmbd_work *work)
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 9f87b6c4393a..26b3d57a8429 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -13,6 +13,7 @@ struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
struct ksmbd_file;
+struct page;
#define KSMBD_WORK_INLINE_IOVS 4
@@ -24,6 +25,8 @@ enum {
struct aux_read {
void *buf;
+ /* Non-NULL for a buffer owned through a compound-page reference. */
+ struct page *page;
struct list_head entry;
};
@@ -45,6 +48,8 @@ struct ksmbd_work {
int iov_alloc_cnt;
int iov_cnt;
int iov_idx;
+ /* Final response prefix before a page-backed last iov; zero disables splice. */
+ unsigned int splice_header_len;
struct kvec iov_inline[KSMBD_WORK_INLINE_IOVS];
/* Next cmd hdr in compound req buf*/
@@ -149,6 +154,8 @@ static inline void *ksmbd_req_buf_next(struct ksmbd_work *work)
struct ksmbd_work *ksmbd_alloc_work_struct(void);
void ksmbd_free_work_struct(struct ksmbd_work *work);
+struct aux_read *ksmbd_alloc_read_buf(size_t length, bool allow_splice);
+void ksmbd_free_read_buf(struct aux_read *ar);
void ksmbd_work_pool_destroy(void);
int ksmbd_work_pool_init(void);
@@ -159,6 +166,8 @@ bool ksmbd_queue_work(struct ksmbd_work *work);
extern bool ksmbd_write_wq_enabled;
bool ksmbd_queue_request(struct ksmbd_work *work, bool use_write_wq);
int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
+ struct aux_read *ar, unsigned int aux_size);
+int ksmbd_iov_pin_rsp_data(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size);
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);
int allocate_interim_rsp_buf(struct ksmbd_work *work);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 5347eba916ed..70cad4e0e8e2 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -351,6 +351,7 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (work->sess)
ksmbd_user_session_put(work->sess);
+ smb2_set_read_splice(work);
ksmbd_conn_write(work);
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 54ecdd096bd2..3d139c686393 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -14,6 +14,8 @@
#include <linux/ethtool.h>
#include <linux/falloc.h>
#include <linux/mount.h>
+#include <linux/mm.h>
+#include <linux/module.h>
#include <linux/filelock.h>
#include <linux/fileattr.h>
#include <linux/timekeeping.h>
@@ -47,6 +49,29 @@
#include "transport_tcp.h"
#include "compress.h"
+/* Switching affects allocation, not ownership of existing READ buffers. */
+static bool read_splice;
+
+static int read_splice_set(const char *val, const struct kernel_param *kp)
+{
+ bool enabled;
+ int ret;
+
+ ret = kstrtobool(val ?: "1", &enabled);
+ if (!ret)
+ WRITE_ONCE(*(bool *)kp->arg, enabled);
+ return ret;
+}
+
+static const struct kernel_param_ops read_splice_ops = {
+ .flags = KERNEL_PARAM_OPS_FL_NOARG,
+ .set = read_splice_set,
+ .get = param_get_bool,
+};
+module_param_cb(read_splice, &read_splice_ops, &read_splice, 0644);
+__MODULE_PARM_TYPE(read_splice, "bool");
+MODULE_PARM_DESC(read_splice, "Page-backed ordinary TCP READ sends");
+
static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
{
if (work->next_smb2_rcv_hdr_off) {
@@ -7700,9 +7725,9 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
if (rc)
goto err_out;
- rc = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
- offsetof(struct smb2_query_info_rsp, Buffer),
- pntsd, secdesclen);
+ rc = ksmbd_iov_pin_rsp_data(work, (void *)rsp,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ pntsd, secdesclen);
err_out:
if (rc) {
rsp->OutputBufferLength = 0;
@@ -8684,7 +8709,7 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
rpc_resp->payload_sz);
nbytes = rpc_resp->payload_sz;
- err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
+ err = ksmbd_iov_pin_rsp_data(work, (void *)rsp,
offsetof(struct smb2_read_rsp, Buffer),
aux_payload_buf, nbytes);
if (err) {
@@ -9004,6 +9029,63 @@ static ssize_t smb2_read_rdma(struct ksmbd_work *work,
return length;
}
+void smb2_set_read_splice(struct ksmbd_work *work)
+{
+ const unsigned int prefix_len = sizeof(__be32);
+ const unsigned int rsp_len = offsetof(struct smb2_read_rsp, Buffer);
+ const unsigned int header_len = prefix_len + rsp_len;
+ const struct smb2_read_rsp *rsp;
+ const struct kvec *payload;
+ const struct aux_read *ar;
+
+ work->splice_header_len = 0;
+ if (!list_is_singular(&work->aux_read_list))
+ return;
+ ar = list_first_entry(&work->aux_read_list, struct aux_read, entry);
+ /* Only eligible TCP READs allocate a page-backed auxiliary buffer. */
+ if (!ar->page)
+ return;
+
+ /* The final response must still be RFC1002 length + READ header + data. */
+ if (work->iov_cnt != 3 || work->iov[0].iov_len != prefix_len ||
+ work->iov[1].iov_len != rsp_len)
+ return;
+ rsp = work->iov[1].iov_base;
+ payload = &work->iov[2];
+
+ /* Signing follows the READ handler; session/tcon refs may be gone. */
+ if (rsp->hdr.Flags & SMB2_FLAGS_SIGNED)
+ return;
+
+ /* Retain the original payload and send only the initialized bytes. */
+ if (payload->iov_base != ar->buf ||
+ !payload->iov_len ||
+ payload->iov_len > page_size(ar->page) ||
+ get_rfc1002_len(work->iov[0].iov_base) + prefix_len !=
+ payload->iov_len + header_len)
+ return;
+
+ /* Do not pin an oversized compound allocation for a short READ. */
+ if (get_order(payload->iov_len) < compound_order(ar->page))
+ return;
+
+ work->splice_header_len = header_len;
+}
+
+static bool smb2_read_splice_eligible(struct ksmbd_work *work,
+ const struct smb2_read_req *req)
+{
+ /* Negotiated signing/encryption capabilities alone do not exclude READs. */
+ return READ_ONCE(read_splice) &&
+ !work->next_smb2_rcv_hdr_off && !req->hdr.NextCommand &&
+ !work->encrypted &&
+ !work->sess->sign && !work->conn->sign &&
+ !(req->hdr.Flags & SMB2_FLAGS_SIGNED) &&
+ !(req->Flags & SMB2_READFLAG_REQUEST_COMPRESSED) &&
+ req->Channel == SMB2_CHANNEL_NONE &&
+ ksmbd_transport_is_tcp(work->conn->transport);
+}
+
/**
* smb2_read() - handler for smb2 read from file
* @work: smb work containing read command buffer
@@ -9013,6 +9095,7 @@ static ssize_t smb2_read_rdma(struct ksmbd_work *work,
int smb2_read(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
+ struct aux_read *ar = NULL;
struct smb2_read_req *req;
struct smb2_read_rsp *rsp;
struct ksmbd_file *fp = NULL;
@@ -9024,7 +9107,6 @@ int smb2_read(struct ksmbd_work *work)
bool is_rdma_channel = false, async_interim = false;
unsigned int max_read_size = conn->vals->max_read_size;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
- void *aux_payload_buf;
ksmbd_debug(SMB, "Received smb2 read request\n");
@@ -9137,15 +9219,14 @@ int smb2_read(struct ksmbd_work *work)
ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n",
fp->filp, offset, length);
- aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
- if (!aux_payload_buf) {
+ ar = ksmbd_alloc_read_buf(length, smb2_read_splice_eligible(work, req));
+ if (!ar) {
err = -ENOMEM;
goto out;
}
- nbytes = ksmbd_vfs_read(work, fp, length, &offset, aux_payload_buf);
+ nbytes = ksmbd_vfs_read(work, fp, length, &offset, ar->buf);
if (nbytes < 0) {
- kvfree(aux_payload_buf);
err = nbytes;
goto out;
}
@@ -9157,10 +9238,10 @@ int smb2_read(struct ksmbd_work *work)
* client. Zero just that tail.
*/
if (nbytes & 7)
- memset(aux_payload_buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes);
+ memset(ar->buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes);
if ((nbytes == 0 && length != 0) || nbytes < mincount) {
- kvfree(aux_payload_buf);
+ ksmbd_free_read_buf(ar);
rsp->hdr.Status = STATUS_END_OF_FILE;
smb2_set_err_rsp(work);
if (async_interim)
@@ -9175,16 +9256,15 @@ int smb2_read(struct ksmbd_work *work)
if (is_rdma_channel == true) {
rdma_transform_len = smb2_prep_rdma_read(work, req,
rsp,
- aux_payload_buf,
+ ar->buf,
nbytes);
if (rdma_transform_len < 0) {
- kvfree(aux_payload_buf);
err = rdma_transform_len;
goto out;
}
/* write data to the client using rdma channel */
remain_bytes = smb2_read_rdma(work, req,
- aux_payload_buf,
+ ar->buf,
nbytes);
if (remain_bytes < 0)
pr_err("RDMA READ transfer failed: session=%llu payload=%zu transform=%d rc=%zd\n",
@@ -9195,8 +9275,8 @@ int smb2_read(struct ksmbd_work *work)
"RDMA READ transfer completed: session=%llu payload=%zu transform=%d\n",
work->sess ? work->sess->id : 0, nbytes,
rdma_transform_len);
- kvfree(aux_payload_buf);
- aux_payload_buf = NULL;
+ ksmbd_free_read_buf(ar);
+ ar = NULL;
nbytes = 0;
if (remain_bytes < 0) {
err = (int)remain_bytes;
@@ -9214,11 +9294,10 @@ int smb2_read(struct ksmbd_work *work)
err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
offsetof(struct smb2_read_rsp, Buffer) +
rdma_transform_len,
- aux_payload_buf, nbytes);
- if (err) {
- kvfree(aux_payload_buf);
+ ar, nbytes);
+ if (err)
goto out;
- }
+ ar = NULL; /* The response now owns the buffer. */
if (async_interim)
release_async_work(work);
/*
@@ -9234,6 +9313,7 @@ int smb2_read(struct ksmbd_work *work)
return 0;
out:
+ ksmbd_free_read_buf(ar);
if (async_interim)
release_async_work(work);
if (err) {
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index 3f08d1ca5a38..27e60f9ff4db 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -429,6 +429,7 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn);
/* smb2 misc functions */
int ksmbd_smb2_check_message(struct ksmbd_work *work);
void smb2_complete_request_open(struct ksmbd_work *work);
+void smb2_set_read_splice(struct ksmbd_work *work);
/* smb2 command handlers */
int smb2_handle_negotiate(struct ksmbd_work *work);
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 1183bb75aa24..5acf183dab64 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -5,6 +5,7 @@
*/
#include <linux/freezer.h>
+#include <linux/page_frag_cache.h>
#include "smb_common.h"
#include "server.h"
@@ -32,6 +33,8 @@ static int bind_additional_ifaces;
struct tcp_transport {
struct ksmbd_transport transport;
struct socket *sock;
+ /* Serialized by conn->srv_mutex. */
+ struct page_frag_cache tx_frag_cache;
struct kvec *iov;
unsigned int nr_iov;
};
@@ -66,6 +69,7 @@ static struct tcp_transport *alloc_transport(struct socket *client_sk)
if (!t)
return NULL;
t->sock = client_sk;
+ page_frag_cache_init(&t->tx_frag_cache);
conn = ksmbd_conn_alloc();
if (!conn) {
@@ -100,6 +104,7 @@ static void ksmbd_tcp_free_transport(struct ksmbd_transport *kt)
struct tcp_transport *t = TCP_TRANS(kt);
sock_release(t->sock);
+ page_frag_cache_drain(&t->tx_frag_cache);
kfree(t->iov);
kfree(t);
}
@@ -431,6 +436,38 @@ static int ksmbd_tcp_read(struct ksmbd_transport *t, char *buf,
return ksmbd_tcp_readv(TCP_TRANS(t), &iov, 1, to_read, max_retries);
}
+/* The last iov is page-backed; consolidate the prefix into a page fragment. */
+static void *ksmbd_tcp_build_splice_iov(struct tcp_transport *t,
+ const struct ksmbd_transport_write *tx,
+ struct kvec *splice_iov)
+{
+ unsigned int header_len = tx->splice_header_len;
+ const struct kvec *payload;
+ struct iov_iter iter;
+ void *header_frag;
+
+ if (!header_len || header_len > PAGE_SIZE || header_len >= tx->size ||
+ tx->iov_cnt < 2)
+ return NULL;
+ payload = &tx->iov[tx->iov_cnt - 1];
+ if (payload->iov_len != tx->size - header_len)
+ return NULL;
+
+ header_frag = page_frag_alloc(&t->tx_frag_cache, header_len,
+ GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN);
+ if (!header_frag)
+ return NULL;
+ iov_iter_kvec(&iter, ITER_SOURCE, tx->iov, tx->iov_cnt, header_len);
+ if (!copy_from_iter_full(header_frag, header_len, &iter)) {
+ page_frag_free(header_frag);
+ return NULL;
+ }
+ splice_iov[0].iov_base = header_frag;
+ splice_iov[0].iov_len = header_len;
+ splice_iov[1] = *payload;
+ return header_frag;
+}
+
static int ksmbd_tcp_writev(struct ksmbd_transport *t,
const struct ksmbd_transport_write *tx)
{
@@ -438,21 +475,35 @@ static int ksmbd_tcp_writev(struct ksmbd_transport *t,
.msg_flags = MSG_NOSIGNAL | tx->msg_flags,
};
struct socket *sock = TCP_TRANS(t)->sock;
+ struct kvec splice_iov[2];
+ void *header_frag;
int ret;
- iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, tx->iov,
- tx->iov_cnt, tx->size);
- /* The caller holds srv_mutex across the entire frame. */
+ /* The caller holds srv_mutex across preparation and the entire frame. */
+ header_frag = ksmbd_tcp_build_splice_iov(TCP_TRANS(t), tx, splice_iov);
+ if (header_frag) {
+ iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, splice_iov,
+ ARRAY_SIZE(splice_iov), tx->size);
+ smb_msg.msg_flags |= MSG_SPLICE_PAGES;
+ } else {
+ iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, tx->iov,
+ tx->iov_cnt, tx->size);
+ }
while (iov_iter_count(&smb_msg.msg_iter)) {
ret = sock_sendmsg(sock, &smb_msg);
if (ret <= 0) {
/* Never append another SMB frame to an incomplete one. */
kernel_sock_shutdown(sock, SHUT_RDWR);
- return ret ?: -EIO;
+ ret = ret ?: -EIO;
+ goto out;
}
}
- return tx->size;
+ ret = tx->size;
+out:
+ if (header_frag)
+ page_frag_free(header_frag);
+ return ret;
}
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
--
2.43.0
next prev parent reply other threads:[~2026-10-22 9:26 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 9:25 [RFC PATCH 0/4] ksmbd: improve WRITE scheduling and TCP READ sends Huiwen He
2026-10-22 9:25 ` [RFC PATCH 1/4] workqueue: export attribute and sysfs setup helpers Huiwen He
2026-10-23 19:58 ` Tejun Heo
2026-10-22 9:25 ` [RFC PATCH 2/4] ksmbd: route WRITE requests to an unbound workqueue Huiwen He
2026-10-24 4:26 ` Namjae Jeon
2026-10-29 11:52 ` hehuiwen
2026-10-22 9:25 ` [RFC PATCH 3/4] ksmbd: complete TCP frames before reporting send success Huiwen He
2026-10-22 9:25 ` Huiwen He [this message]
2026-10-24 4:44 ` [RFC PATCH 4/4] ksmbd: send READ payloads with MSG_SPLICE_PAGES Namjae Jeon
2026-10-29 15:50 ` hehuiwen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261022092514.2590010-5-huiwen.he@linux.dev \
--to=huiwen.he@linux.dev \
--cc=chenxiaosong@chenxiaosong.com \
--cc=linkinjeon@kernel.org \
--cc=linux-cifs@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=senozhatsky@chromium.org \
--cc=tj@kernel.org \
--cc=tom@talpey.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.