* [RFC 0/3] ksmbd: use splice for SMB2 READ responses
@ 2026-07-13 7:11 wang zhaolong
2026-07-13 7:11 ` [RFC 1/3] ksmbd: add read payload infrastructure wang zhaolong
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: wang zhaolong @ 2026-07-13 7:11 UTC (permalink / raw)
To: linux-cifs
Cc: linkinjeon, smfrench, senozhatsky, tom, linux-kernel,
Wang Zhaolong
From: Wang Zhaolong <wangzhaolong@fnnas.com>
SMB2 READ currently copies file data into an allocated auxiliary buffer
before sending the response. This series adds a page-backed payload,
collects page-cache data with splice_direct_to_actor(), and sends it after
the SMB response header with MSG_SPLICE_PAGES.
This RFC deliberately limits the new path to plain TCP reads of at least
64 KiB. Signed, encrypted, compressed, compound, RDMA, stream, non-regular,
O_DIRECT and DAX reads continue to use the buffered path. Unsupported
splice collection also falls back before the response owns the payload. If
a socket error occurs after part of a PDU has been sent, the connection is
shut down rather than reusing a corrupted byte stream.
I would particularly appreciate feedback on the work-owned bio_vec payload,
the transport hook, and whether splice_direct_to_actor() is an appropriate
interface for this path.
I tested the series in QEMU with ksmbd and the SMB client in the same
guest. The smb2.read.eof tests passed, including zero-length and
MinimumCount cases. Reads of 65535 bytes used the buffered path, while
65536-byte and 1 MiB reads used the splice payload path. Forced signed and
encrypted 1 MiB reads stayed on the buffered path. All data checks matched
and no kernel warning was observed.
For an exploratory performance comparison, I used a warm 4 GiB file over
SMB 3.1.1 with cache=none, 1 MiB synchronous reads and one job. Median
results across eight valid 60-second samples per kernel were:
baseline patched delta
bandwidth 1.728 GB/s 2.246 GB/s +30.0%
mean completion latency 604.1 us 464.5 us -23.1%
median sample P99 latency 794.6 us 606.2 us -23.7%
One patched-kernel sample with TCP retransmissions was discarded by the
validity gate and replaced. These are loopback results; an independent
client is still needed to measure network-path and server CPU effects. The
benchmark candidate preceded the final zero-length READ ordering fix, which
did not change the positive-length payload path exercised here.
Compression, large compound READ, RDMA, stream, non-regular, DAX and
O_DIRECT exclusions, and the unsupported-splice fallback, were not
exercised by this lab.
Wang Zhaolong (3):
ksmbd: add read payload infrastructure
ksmbd: add splice-based read payload helper
ksmbd: use splice payloads for simple SMB2 READ
fs/smb/server/connection.c | 25 ++++-
fs/smb/server/connection.h | 4 +
fs/smb/server/ksmbd_work.c | 45 ++++++++
fs/smb/server/ksmbd_work.h | 12 +++
fs/smb/server/smb2pdu.c | 67 ++++++++++++
fs/smb/server/transport_tcp.c | 58 ++++++++++
fs/smb/server/vfs.c | 195 ++++++++++++++++++++++++++++++----
fs/smb/server/vfs.h | 4 +
8 files changed, 382 insertions(+), 28 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC 1/3] ksmbd: add read payload infrastructure
2026-07-13 7:11 [RFC 0/3] ksmbd: use splice for SMB2 READ responses wang zhaolong
@ 2026-07-13 7:11 ` wang zhaolong
2026-07-13 7:11 ` [RFC 2/3] ksmbd: add splice-based read payload helper wang zhaolong
2026-07-13 7:11 ` [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ wang zhaolong
2 siblings, 0 replies; 7+ messages in thread
From: wang zhaolong @ 2026-07-13 7:11 UTC (permalink / raw)
To: linux-cifs
Cc: linkinjeon, smfrench, senozhatsky, tom, linux-kernel,
Wang Zhaolong
From: Wang Zhaolong <wangzhaolong@fnnas.com>
ksmbd currently copies SMB READ data into an allocated auxiliary buffer
before sending the response. A splice-based path needs to keep page-backed
file data separate from the SMB header while still accounting for it in the
RFC1002 length.
Add a work-owned READ payload made of bio_vecs and an ownership-transfer
helper for response pinning. Add a transport operation and a TCP
implementation that sends the header followed by the payload with
MSG_SPLICE_PAGES while the connection lock prevents interleaving.
Handle positive short sends and shut down the socket if a partially emitted
PDU fails. The existing READ path remains unchanged until the payload
collector is wired up.
Signed-off-by: Wang Zhaolong <wangzhaolong@fnnas.com>
---
fs/smb/server/connection.c | 25 ++++++++++++---
fs/smb/server/connection.h | 4 +++
fs/smb/server/ksmbd_work.c | 45 +++++++++++++++++++++++++++
fs/smb/server/ksmbd_work.h | 12 ++++++++
fs/smb/server/transport_tcp.c | 58 +++++++++++++++++++++++++++++++++++
5 files changed, 139 insertions(+), 5 deletions(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 9e8fdb39e5a2..97ad736bf87a 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -351,10 +351,12 @@ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
}
int ksmbd_conn_write(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
+ unsigned int head_len;
+ unsigned int pdu_len;
int sent;
if (!work->response_buf) {
pr_err("NULL response header\n");
return -EINVAL;
@@ -364,16 +366,29 @@ int ksmbd_conn_write(struct ksmbd_work *work)
return 0;
if (!work->iov_idx)
return -EINVAL;
+ pdu_len = get_rfc1002_len(work->iov[0].iov_base) + 4;
ksmbd_conn_lock(conn);
- sent = conn->transport->ops->writev(conn->transport, work->iov,
- work->iov_cnt,
- get_rfc1002_len(work->iov[0].iov_base) + 4,
- work->need_invalidate_rkey,
- work->remote_key);
+ if (work->read_payload) {
+ if (WARN_ON_ONCE(work->read_payload->len > pdu_len)) {
+ sent = -EINVAL;
+ } else if (WARN_ON_ONCE(!conn->transport->ops->write_read_payload)) {
+ sent = -EOPNOTSUPP;
+ } else {
+ head_len = pdu_len - work->read_payload->len;
+ sent = conn->transport->ops->write_read_payload(conn->transport,
+ work->iov, work->iov_cnt, head_len,
+ work->read_payload);
+ }
+ } else {
+ sent = conn->transport->ops->writev(conn->transport, work->iov,
+ work->iov_cnt, pdu_len,
+ work->need_invalidate_rkey,
+ work->remote_key);
+ }
ksmbd_conn_unlock(conn);
if (sent < 0) {
pr_err("Failed to send message: %d\n", sent);
return sent;
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index ec75633b7da0..a44e382138a7 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -138,10 +138,14 @@ struct ksmbd_transport_ops {
int (*read)(struct ksmbd_transport *t, char *buf,
unsigned int size, int max_retries);
int (*writev)(struct ksmbd_transport *t, struct kvec *iovs, int niov,
int size, bool need_invalidate_rkey,
unsigned int remote_key);
+ int (*write_read_payload)(struct ksmbd_transport *t,
+ struct kvec *iovs, int niov,
+ unsigned int head_len,
+ struct ksmbd_read_payload *payload);
int (*rdma_read)(struct ksmbd_transport *t,
void *buf, unsigned int len,
struct smbdirect_buffer_descriptor_v1 *desc,
unsigned int desc_len);
int (*rdma_write)(struct ksmbd_transport *t,
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index e2c2f45264be..05361f6aa8fb 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2019 Samsung Electronics Co., Ltd.
*/
+#include <linux/bvec.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
@@ -14,10 +15,23 @@
#include "mgmt/ksmbd_ida.h"
static struct kmem_cache *work_cache;
static struct workqueue_struct *ksmbd_wq;
+void ksmbd_read_payload_release(struct ksmbd_read_payload *payload)
+{
+ unsigned int i;
+
+ if (!payload)
+ return;
+
+ for (i = 0; i < payload->nr_bvecs; i++)
+ put_page(payload->bvec[i].bv_page);
+ kvfree(payload->bvec);
+ kfree(payload);
+}
+
static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
{
struct kvec *new;
int new_alloc_cnt = work->iov_alloc_cnt;
@@ -74,10 +88,11 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
list_for_each_entry_safe(ar, tmp, &work->aux_read_list, entry) {
kvfree(ar->buf);
list_del(&ar->entry);
kfree(ar);
}
+ ksmbd_read_payload_release(work->read_payload);
kfree(work->tr_buf);
kvfree(work->compress_buf);
kvfree(work->request_buf);
if (work->iov != work->iov_inline)
@@ -179,10 +194,40 @@ int ksmbd_iov_pin_rsp_read(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);
}
+/**
+ * ksmbd_iov_pin_rsp_read_payload() - attach a page-backed READ payload
+ * @work: smb work that owns the response
+ * @ib: response header
+ * @len: response header length
+ * @payload: payload whose ownership transfers to @work only on success
+ *
+ * Return: 0 on success, otherwise a negative error with ownership retained by
+ * the caller.
+ */
+int ksmbd_iov_pin_rsp_read_payload(struct ksmbd_work *work, void *ib, int len,
+ struct ksmbd_read_payload *payload)
+{
+ int ret;
+
+ if (!payload || !payload->bvec || !payload->nr_bvecs || !payload->len)
+ return -EINVAL;
+ if (WARN_ON_ONCE(work->read_payload) ||
+ WARN_ON_ONCE(payload->len > INT_MAX))
+ return -EINVAL;
+
+ ret = __ksmbd_iov_pin_rsp(work, ib, len, NULL, 0);
+ if (ret)
+ return ret;
+
+ inc_rfc1001_len(work->iov[0].iov_base, payload->len);
+ work->read_payload = payload;
+ return 0;
+}
+
int allocate_interim_rsp_buf(struct ksmbd_work *work)
{
work->response_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE, KSMBD_DEFAULT_GFP);
if (!work->response_buf)
return -ENOMEM;
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 88104f0cf363..c0e1370e2599 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -10,10 +10,11 @@
#include <linux/workqueue.h>
struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
+struct bio_vec;
#define KSMBD_WORK_INLINE_IOVS 4
enum {
KSMBD_WORK_ACTIVE = 0,
@@ -24,10 +25,17 @@ enum {
struct aux_read {
void *buf;
struct list_head entry;
};
+struct ksmbd_read_payload {
+ struct bio_vec *bvec;
+ unsigned int nr_bvecs;
+ unsigned int nr_alloc;
+ size_t len;
+};
+
/* one of these for every pending CIFS request at the connection */
struct ksmbd_work {
/* Server corresponding to this mid */
struct ksmbd_conn *conn;
struct ksmbd_session *sess;
@@ -37,10 +45,11 @@ struct ksmbd_work {
void *request_buf;
/* Response buffer */
void *response_buf;
struct list_head aux_read_list;
+ struct ksmbd_read_payload *read_payload;
struct kvec *iov;
int iov_alloc_cnt;
int iov_cnt;
int iov_idx;
@@ -142,8 +151,11 @@ int ksmbd_work_pool_init(void);
int ksmbd_workqueue_init(void);
void ksmbd_workqueue_destroy(void);
bool ksmbd_queue_work(struct ksmbd_work *work);
int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
void *aux_buf, unsigned int aux_size);
+int ksmbd_iov_pin_rsp_read_payload(struct ksmbd_work *work, void *ib, int len,
+ struct ksmbd_read_payload *payload);
+void ksmbd_read_payload_release(struct ksmbd_read_payload *payload);
int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);
int allocate_interim_rsp_buf(struct ksmbd_work *work);
#endif /* __KSMBD_WORK_H__ */
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 13b711ea575d..3095898d74d1 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -425,10 +425,67 @@ static int ksmbd_tcp_writev(struct ksmbd_transport *t, struct kvec *iov,
struct msghdr smb_msg = {.msg_flags = MSG_NOSIGNAL};
return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, iov, nvecs, size);
}
+static int ksmbd_tcp_send_iter(struct socket *sock, struct iov_iter *iter,
+ unsigned int flags, bool *sent_any)
+{
+ struct msghdr msg = { .msg_flags = MSG_NOSIGNAL | flags };
+ int sent = 0;
+
+ while (iov_iter_count(iter)) {
+ int ret;
+
+ msg.msg_iter = *iter;
+ ret = sock_sendmsg(sock, &msg);
+ if (ret <= 0)
+ return ret ?: -EPIPE;
+ *sent_any = true;
+ iov_iter_advance(iter, ret);
+ sent += ret;
+ }
+ return sent;
+}
+
+static int ksmbd_tcp_write_read_payload(struct ksmbd_transport *t,
+ struct kvec *iov, int nvecs,
+ unsigned int head_len,
+ struct ksmbd_read_payload *payload)
+{
+ struct tcp_transport *tcp = TCP_TRANS(t);
+ struct iov_iter iter;
+ bool sent_any = false;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < payload->nr_bvecs; i++) {
+ if (!sendpages_ok(payload->bvec[i].bv_page,
+ payload->bvec[i].bv_len,
+ payload->bvec[i].bv_offset))
+ return -EOPNOTSUPP;
+ }
+
+ iov_iter_kvec(&iter, ITER_SOURCE, iov, nvecs, head_len);
+ ret = ksmbd_tcp_send_iter(tcp->sock, &iter, MSG_MORE, &sent_any);
+ if (ret < 0)
+ goto out;
+ iov_iter_bvec(&iter, ITER_SOURCE, payload->bvec, payload->nr_bvecs,
+ payload->len);
+ ret = ksmbd_tcp_send_iter(tcp->sock, &iter, MSG_SPLICE_PAGES,
+ &sent_any);
+ if (ret >= 0)
+ return head_len + ret;
+out:
+ /* A partially emitted RFC1002 PDU makes this TCP stream unusable. */
+ if (sent_any) {
+ ksmbd_conn_set_exiting(t->conn);
+ kernel_sock_shutdown(tcp->sock, SHUT_RDWR);
+ }
+ return ret;
+}
+
static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
{
free_transport(TCP_TRANS(t));
if (server_conf.max_connections)
atomic_dec(&active_num_conn);
@@ -677,8 +734,9 @@ int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz)
}
static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
.read = ksmbd_tcp_read,
.writev = ksmbd_tcp_writev,
+ .write_read_payload = ksmbd_tcp_write_read_payload,
.disconnect = ksmbd_tcp_disconnect,
.free_transport = ksmbd_tcp_free_transport,
};
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC 2/3] ksmbd: add splice-based read payload helper
2026-07-13 7:11 [RFC 0/3] ksmbd: use splice for SMB2 READ responses wang zhaolong
2026-07-13 7:11 ` [RFC 1/3] ksmbd: add read payload infrastructure wang zhaolong
@ 2026-07-13 7:11 ` wang zhaolong
2026-07-13 7:11 ` [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ wang zhaolong
2 siblings, 0 replies; 7+ messages in thread
From: wang zhaolong @ 2026-07-13 7:11 UTC (permalink / raw)
To: linux-cifs
Cc: linkinjeon, smfrench, senozhatsky, tom, linux-kernel,
Wang Zhaolong
From: Wang Zhaolong <wangzhaolong@fnnas.com>
Add ksmbd_vfs_read_payload() to collect page-cache backed splice buffers
into a bio_vec payload. The actor takes a reference to every page so the
payload remains valid after the internal splice pipe is drained.
Preserve the buffered READ path's directory, zero-length, access and
byte-range lock semantics. Streams, non-regular files, O_DIRECT, DAX and
files without splice_read support return -EOPNOTSUPP with the offset
unchanged, allowing the caller to retry through kernel_read(). A short read
before EOF is handled the same way to preserve the existing READ data path.
Signed-off-by: Wang Zhaolong <wangzhaolong@fnnas.com>
---
fs/smb/server/vfs.c | 195 ++++++++++++++++++++++++++++++++++++++------
fs/smb/server/vfs.h | 4 +
2 files changed, 176 insertions(+), 23 deletions(-)
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index d0a0ad15d803..3a3f1b8e2d33 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -17,10 +17,13 @@
#include <linux/dcache.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sched/xacct.h>
#include <linux/crc32c.h>
+#include <linux/net.h>
+#include <linux/overflow.h>
+#include <linux/pipe_fs_i.h>
#include <linux/splice.h>
#include <linux/fileattr.h>
#include "glob.h"
#include "oplock.h"
@@ -326,10 +329,33 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end,
out:
spin_unlock(&ctx->flc_lock);
return error;
}
+static int ksmbd_vfs_check_read_access(struct ksmbd_work *work,
+ struct ksmbd_file *fp)
+{
+ if (work->conn->connection_type &&
+ !(fp->daccess & (FILE_READ_DATA_LE | FILE_EXECUTE_LE))) {
+ pr_err("no right to read(%pD)\n", fp->filp);
+ return -EACCES;
+ }
+ return 0;
+}
+
+static int ksmbd_vfs_check_read_range(struct ksmbd_work *work,
+ struct ksmbd_file *fp, loff_t pos,
+ size_t count)
+{
+ if (!work->tcon->posix_extensions &&
+ check_lock_range(fp->filp, pos, pos + count - 1, READ)) {
+ pr_err("unable to read due to lock\n");
+ return -EAGAIN;
+ }
+ return 0;
+}
+
/**
* ksmbd_vfs_read() - vfs helper for smb file read
* @work: smb work
* @fp: ksmbd file pointer
* @count: read byte count
@@ -340,47 +366,170 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end,
*/
int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
loff_t *pos, char *rbuf)
{
struct file *filp = fp->filp;
- ssize_t nbytes = 0;
- struct inode *inode = file_inode(filp);
+ ssize_t nbytes;
+ int ret;
- if (S_ISDIR(inode->i_mode))
+ if (S_ISDIR(file_inode(filp)->i_mode))
return -EISDIR;
-
if (unlikely(count == 0))
return 0;
-
- if (work->conn->connection_type) {
- if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_EXECUTE_LE))) {
- pr_err("no right to read(%pD)\n", fp->filp);
- return -EACCES;
- }
- }
-
+ ret = ksmbd_vfs_check_read_access(work, fp);
+ if (ret)
+ return ret;
if (ksmbd_stream_fd(fp))
return ksmbd_vfs_stream_read(fp, rbuf, pos, count);
-
- if (!work->tcon->posix_extensions) {
- int ret;
-
- ret = check_lock_range(filp, *pos, *pos + count - 1, READ);
- if (ret) {
- pr_err("unable to read due to lock\n");
- return -EAGAIN;
- }
- }
+ ret = ksmbd_vfs_check_read_range(work, fp, *pos, count);
+ if (ret)
+ return ret;
nbytes = kernel_read(filp, rbuf, count, pos);
if (nbytes < 0) {
pr_err("smb read failed, err = %zd\n", nbytes);
return nbytes;
}
+ filp->f_pos = *pos;
+ ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, nbytes);
+ return nbytes;
+}
+
+static int ksmbd_read_payload_reserve(struct ksmbd_read_payload *payload,
+ unsigned int nr_bvecs)
+{
+ struct bio_vec *bvec;
+ unsigned int needed, nr_alloc;
+ if (check_add_overflow(payload->nr_bvecs, nr_bvecs, &needed))
+ return -EOVERFLOW;
+ if (payload->nr_alloc >= needed)
+ return 0;
+ nr_alloc = payload->nr_alloc ?: 16;
+ while (nr_alloc < needed) {
+ if (nr_alloc > UINT_MAX / 2) {
+ nr_alloc = needed;
+ break;
+ }
+ nr_alloc *= 2;
+ }
+ bvec = kvrealloc(payload->bvec,
+ array_size(nr_alloc, sizeof(*payload->bvec)),
+ KSMBD_DEFAULT_GFP | __GFP_ZERO);
+ if (!bvec)
+ return -ENOMEM;
+ payload->bvec = bvec;
+ payload->nr_alloc = nr_alloc;
+ return 0;
+}
+
+static int ksmbd_read_payload_actor(struct pipe_inode_info *pipe,
+ struct pipe_buffer *buf,
+ struct splice_desc *sd)
+{
+ struct ksmbd_read_payload *payload = sd->u.data;
+ unsigned int offset = buf->offset, len = sd->len;
+ size_t payload_len;
+ unsigned int nr_bvecs;
+ int ret;
+
+ nr_bvecs = DIV_ROUND_UP(offset_in_page(offset) + len, PAGE_SIZE);
+ ret = ksmbd_read_payload_reserve(payload, nr_bvecs);
+ if (ret)
+ return ret;
+ if (check_add_overflow(payload->len, (size_t)len, &payload_len))
+ return -EOVERFLOW;
+ while (len) {
+ struct page *page = buf->page + offset / PAGE_SIZE;
+ unsigned int page_offset = offset_in_page(offset);
+ unsigned int bytes = min_t(unsigned int, len,
+ PAGE_SIZE - page_offset);
+
+ if (!sendpage_ok(page))
+ return -EOPNOTSUPP;
+ get_page(page);
+ bvec_set_page(&payload->bvec[payload->nr_bvecs++], page,
+ bytes, page_offset);
+ offset += bytes;
+ len -= bytes;
+ }
+ payload->len = payload_len;
+ return sd->len;
+}
+
+static int ksmbd_direct_splice_actor(struct pipe_inode_info *pipe,
+ struct splice_desc *sd)
+{
+ return __splice_from_pipe(pipe, sd, ksmbd_read_payload_actor);
+}
+
+/**
+ * ksmbd_vfs_read_payload() - collect file pages for an SMB READ response
+ * @work: smb work
+ * @fp: ksmbd file pointer
+ * @count: read byte count
+ * @pos: file position, advanced only on success
+ * @payload: payload returned on success, otherwise set to NULL
+ *
+ * Return: number of bytes read, or -EOPNOTSUPP with @pos unchanged when the
+ * caller may safely retry through ksmbd_vfs_read().
+ */
+int ksmbd_vfs_read_payload(struct ksmbd_work *work, struct ksmbd_file *fp,
+ size_t count, loff_t *pos,
+ struct ksmbd_read_payload **payload)
+{
+ struct file *filp = fp->filp;
+ struct ksmbd_read_payload *read_payload;
+ loff_t start = *pos;
+ struct splice_desc sd;
+ ssize_t nbytes;
+ int ret;
+
+ *payload = NULL;
+ if (S_ISDIR(file_inode(filp)->i_mode))
+ return -EISDIR;
+ if (unlikely(count == 0))
+ return 0;
+ ret = ksmbd_vfs_check_read_access(work, fp);
+ if (ret)
+ return ret;
+ if (ksmbd_stream_fd(fp) || !S_ISREG(file_inode(filp)->i_mode) ||
+ (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)) ||
+ !(filp->f_mode & FMODE_LSEEK) || !filp->f_op->splice_read)
+ return -EOPNOTSUPP;
+ ret = ksmbd_vfs_check_read_range(work, fp, start, count);
+ if (ret)
+ return ret;
+
+ read_payload = kzalloc_obj(struct ksmbd_read_payload,
+ KSMBD_DEFAULT_GFP);
+ if (!read_payload)
+ return -ENOMEM;
+ nbytes = rw_verify_area(READ, filp, pos, count);
+ if (nbytes)
+ goto out_free;
+ sd = (struct splice_desc) {
+ .total_len = count,
+ .pos = start,
+ .u.data = read_payload,
+ };
+ nbytes = splice_direct_to_actor(filp, &sd, ksmbd_direct_splice_actor);
+ if (nbytes <= 0)
+ goto out_free;
+ if (nbytes < count && start + nbytes < i_size_read(file_inode(filp))) {
+ nbytes = -EOPNOTSUPP;
+ goto out_free;
+ }
+ *pos = start + nbytes;
filp->f_pos = *pos;
- ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, (s64)nbytes);
+ ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, nbytes);
+ *payload = read_payload;
+ return nbytes;
+
+out_free:
+ *pos = start;
+ ksmbd_read_payload_release(read_payload);
return nbytes;
}
static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
size_t count)
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 7b3d2f4fd985..5afee75c4a3b 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -34,10 +34,11 @@ enum {
#define CREATE_OPTION_SPECIAL 0x20000000
struct ksmbd_work;
struct ksmbd_file;
struct ksmbd_conn;
+struct ksmbd_read_payload;
struct ksmbd_dir_info {
const char *name;
char *wptr;
char *rptr;
@@ -77,10 +78,13 @@ void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap,
struct dentry *dentry, __le32 *daccess);
int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode);
int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode);
int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
loff_t *pos, char *rbuf);
+int ksmbd_vfs_read_payload(struct ksmbd_work *work, struct ksmbd_file *fp,
+ size_t count, loff_t *pos,
+ struct ksmbd_read_payload **payload);
int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
char *buf, size_t count, loff_t *pos, bool sync,
ssize_t *written);
int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id);
int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path);
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ
2026-07-13 7:11 [RFC 0/3] ksmbd: use splice for SMB2 READ responses wang zhaolong
2026-07-13 7:11 ` [RFC 1/3] ksmbd: add read payload infrastructure wang zhaolong
2026-07-13 7:11 ` [RFC 2/3] ksmbd: add splice-based read payload helper wang zhaolong
@ 2026-07-13 7:11 ` wang zhaolong
2026-07-13 8:58 ` Namjae Jeon
2 siblings, 1 reply; 7+ messages in thread
From: wang zhaolong @ 2026-07-13 7:11 UTC (permalink / raw)
To: linux-cifs
Cc: linkinjeon, smfrench, senozhatsky, tom, linux-kernel,
Wang Zhaolong
From: Wang Zhaolong <wangzhaolong@fnnas.com>
Use the page-backed payload path for plain TCP SMB2 READ requests of at
least 64 KiB. Unsupported collection results fall back before response
ownership is committed; permission, locking and allocation failures retain
the existing error handling.
Keep signed, encrypted, compressed, compound and RDMA requests on the
buffered path because they require signing, response transforms, compound
response layout, or alternate transport handling. Streams, non-regular
files, O_DIRECT and DAX reads also remain on the buffered path.
Signed-off-by: Wang Zhaolong <wangzhaolong@fnnas.com>
---
fs/smb/server/smb2pdu.c | 67 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index b73167785e87..a4a80ecc6d2f 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -7304,10 +7304,38 @@ static ssize_t smb2_read_rdma_channel(struct ksmbd_work *work,
return err;
return length;
}
+#define KSMBD_READ_PAYLOAD_MIN_IO_SIZE SZ_64K
+
+static bool smb2_read_payload_allowed(struct ksmbd_work *work,
+ struct smb2_read_req *req,
+ struct ksmbd_file *fp,
+ size_t length, bool is_rdma_channel)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct file *filp = fp->filp;
+
+ if (is_rdma_channel || length < KSMBD_READ_PAYLOAD_MIN_IO_SIZE ||
+ !conn->transport->ops->write_read_payload)
+ return false;
+ if (req->Flags & SMB2_READFLAG_REQUEST_COMPRESSED)
+ return false;
+ if (work->next_smb2_rcv_hdr_off || le32_to_cpu(req->hdr.NextCommand))
+ return false;
+ if (work->encrypted)
+ return false;
+ if (work->sess &&
+ (work->sess->sign || conn->ops->is_sign_req(work, SMB2_READ_HE)))
+ return false;
+ if (ksmbd_stream_fd(fp) || !S_ISREG(file_inode(filp)->i_mode) ||
+ (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)))
+ return false;
+ return true;
+}
+
/**
* smb2_read() - handler for smb2 read from file
* @work: smb work containing read command buffer
*
* Return: 0 on success, otherwise error
@@ -7316,10 +7344,11 @@ int smb2_read(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_read_req *req;
struct smb2_read_rsp *rsp;
struct ksmbd_file *fp = NULL;
+ struct ksmbd_read_payload *payload = NULL;
loff_t offset;
size_t length, mincount;
ssize_t nbytes = 0, remain_bytes = 0;
int err = 0;
bool is_rdma_channel = false, async_interim = false;
@@ -7418,10 +7447,48 @@ int smb2_read(struct ksmbd_work *work)
}
ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n",
fp->filp, offset, length);
+ if (smb2_read_payload_allowed(work, req, fp, length,
+ is_rdma_channel)) {
+ nbytes = ksmbd_vfs_read_payload(work, fp, length, &offset,
+ &payload);
+ if (nbytes == -EOPNOTSUPP)
+ goto read_fallback;
+ if (nbytes < 0) {
+ err = nbytes;
+ goto out;
+ }
+ if ((nbytes == 0 && length != 0) || nbytes < mincount) {
+ ksmbd_read_payload_release(payload);
+ rsp->hdr.Status = STATUS_END_OF_FILE;
+ smb2_set_err_rsp(work);
+ ksmbd_fd_put(work, fp);
+ return -ENODATA;
+ }
+
+ rsp->StructureSize = cpu_to_le16(17);
+ rsp->DataOffset = 80;
+ rsp->Reserved = 0;
+ rsp->DataLength = cpu_to_le32(nbytes);
+ rsp->DataRemaining = 0;
+ rsp->Flags = 0;
+ err = ksmbd_iov_pin_rsp_read_payload(work, rsp,
+ offsetof(struct smb2_read_rsp, Buffer),
+ payload);
+ if (err) {
+ ksmbd_read_payload_release(payload);
+ goto out;
+ }
+ ksmbd_fd_put(work, fp);
+ return 0;
+ }
+
+read_fallback:
+ payload = NULL;
+
aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
if (!aux_payload_buf) {
err = -ENOMEM;
goto out;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ
2026-07-13 7:11 ` [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ wang zhaolong
@ 2026-07-13 8:58 ` Namjae Jeon
2026-07-13 9:36 ` Wang Zhaolong
2026-07-17 12:49 ` Wang Zhaolong
0 siblings, 2 replies; 7+ messages in thread
From: Namjae Jeon @ 2026-07-13 8:58 UTC (permalink / raw)
To: wang zhaolong; +Cc: linux-cifs, smfrench, senozhatsky, tom, linux-kernel
On Mon, Jul 13, 2026 at 4:18 PM wang zhaolong <wangzhaolong@fnnas.com> wrote:
>
> From: Wang Zhaolong <wangzhaolong@fnnas.com>
Hi Wang,
Could you rebase the series on the current #ksmbd-for-next-next branch
to test this ?
>
> Use the page-backed payload path for plain TCP SMB2 READ requests of at
> least 64 KiB.
Could you provide benchmark results ? and requests smaller than 64 KiB
are unlikely to benefit from this optimization? Theoretically, it
seems like it would improve, but I am wondering how much performance
improvement there actually is.
> Unsupported collection results fall back before response
> ownership is committed; permission, locking and allocation failures retain
> the existing error handling.
>
> Keep signed, encrypted, compressed, compound and RDMA requests on the
> buffered path because they require signing, response transforms, compound
> response layout, or alternate transport handling. Streams, non-regular
> files, O_DIRECT and DAX reads also remain on the buffered path.
>
> Signed-off-by: Wang Zhaolong <wangzhaolong@fnnas.com>
> ---
> fs/smb/server/smb2pdu.c | 67 +++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 67 insertions(+)
>
> diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
> index b73167785e87..a4a80ecc6d2f 100644
> --- a/fs/smb/server/smb2pdu.c
> +++ b/fs/smb/server/smb2pdu.c
> @@ -7304,10 +7304,38 @@ static ssize_t smb2_read_rdma_channel(struct ksmbd_work *work,
> return err;
>
> return length;
> }
>
> +#define KSMBD_READ_PAYLOAD_MIN_IO_SIZE SZ_64K
> +
> +static bool smb2_read_payload_allowed(struct ksmbd_work *work,
> + struct smb2_read_req *req,
> + struct ksmbd_file *fp,
> + size_t length, bool is_rdma_channel)
> +{
> + struct ksmbd_conn *conn = work->conn;
> + struct file *filp = fp->filp;
> +
> + if (is_rdma_channel || length < KSMBD_READ_PAYLOAD_MIN_IO_SIZE ||
> + !conn->transport->ops->write_read_payload)
> + return false;
> + if (req->Flags & SMB2_READFLAG_REQUEST_COMPRESSED)
> + return false;
> + if (work->next_smb2_rcv_hdr_off || le32_to_cpu(req->hdr.NextCommand))
> + return false;
> + if (work->encrypted)
> + return false;
> + if (work->sess &&
> + (work->sess->sign || conn->ops->is_sign_req(work, SMB2_READ_HE)))
> + return false;
> + if (ksmbd_stream_fd(fp) || !S_ISREG(file_inode(filp)->i_mode) ||
> + (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)))
Since ksmbd does not currently open files with O_DIRECT, is the
O_DIRECT check needed here, or is it only intended as a defensive
guard for future support?
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ
2026-07-13 8:58 ` Namjae Jeon
@ 2026-07-13 9:36 ` Wang Zhaolong
2026-07-17 12:49 ` Wang Zhaolong
1 sibling, 0 replies; 7+ messages in thread
From: Wang Zhaolong @ 2026-07-13 9:36 UTC (permalink / raw)
To: Namjae Jeon; +Cc: linux-cifs, smfrench, senozhatsky, tom, linux-kernel
Hi Namjae,
Thank you for the quick review.
> From: "Namjae Jeon"<linkinjeon@kernel.org>
> Date: Mon, Jul 13, 2026, 4:59 PM
> Subject: Re: [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ
> To: "wang zhaolong"<wangzhaolong@fnnas.com>
> Cc: <linux-cifs@vger.kernel.org>, <smfrench@gmail.com>, <senozhatsky@chromium.org>, <tom@talpey.com>, <linux-kernel@vger.kernel.org>
> On Mon, Jul 13, 2026 at 4:18 PM wang zhaolong <wangzhaolong@fnnas.com> wrote:
> >
> > From: Wang Zhaolong <wangzhaolong@fnnas.com>
> Hi Wang,
>
> Could you rebase the series on the current #ksmbd-for-next-next branch
> to test this ?
Okay, I will rebase the series onto the current ksmbd-for-next-next branch and rerun
the functional and performance tests.
> >
> > Use the page-backed payload path for plain TCP SMB2 READ requests of at
> > least 64 KiB.
> Could you provide benchmark results ? and requests smaller than 64 KiB
> are unlikely to benefit from this optimization? Theoretically, it
> seems like it would improve, but I am wondering how much performance
> improvement there actually is.
Regarding the 64 KiB threshold, it is currently a conservative value rather than
a conclusion based on complete measurements. As noted in the cover letter,
I have already included initial results based on 1 MiB sequential reads, where
the splice path showed a clear improvement:
baseline patched delta
bandwidth 1.728 GB/s 2.246 GB/s +30.0%
mean completion latency 604.1 us 464.5 us -23.1%
median sample P99 latency 794.6 us 606.2 us -23.7%
These results were obtained in a QEMU environment with ksmbd and the SMB
client in the same guest, using a warm 4 GiB file, SMB 3.1.1, cache=none, 1 MiB
synchronous reads, and one job.
I agree that smaller requests may also benefit. However, for small reads, the
overhead of managing pages and pipes might cancel out some of the gains
from reducing copies, so I chose 64 KiB as a conservative starting threshold.
I will add benchmarks for a range of request sizes, for example 4 KiB, 16 KiB, 32 KiB,
64 KiB, 128 KiB, 256 KiB, and 1 MiB. Based on those results, I can either adjust the
threshold or remove it if the splice path remains beneficial for smaller requests.
> > + return false;
> > + if (ksmbd_stream_fd(fp) || !S_ISREG(file_inode(filp)->i_mode) ||
> > + (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)))
> Since ksmbd does not currently open files with O_DIRECT, is the
> O_DIRECT check needed here, or is it only intended as a defensive
> guard for future support?
>
The O_DIRECT check is currently only a defensive guard. I added it
to ensure that the page-cache-backed payload path is not accidentally
used if direct-I/O support or another file-opening mode is introduced later.
I can remove the check if you prefer to avoid guarding an unsupported
configuration, or keep it with a comment explaining that it is intended for
future-proofing.
Best regards,
Wang Zhaolong
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ
2026-07-13 8:58 ` Namjae Jeon
2026-07-13 9:36 ` Wang Zhaolong
@ 2026-07-17 12:49 ` Wang Zhaolong
1 sibling, 0 replies; 7+ messages in thread
From: Wang Zhaolong @ 2026-07-17 12:49 UTC (permalink / raw)
To: Namjae Jeon
Cc: linux-cifs, Wang Zhaolong, smfrench, senozhatsky, tom,
linux-kernel
On Mon, Jul 13, 2026 at 05:58:46PM +0900, Namjae Jeon wrote:
> On Mon, Jul 13, 2026 at 4:18 PM wang zhaolong <wangzhaolong@fnnas.com> wrote:
> >
> > From: Wang Zhaolong <wangzhaolong@fnnas.com>
> Hi Wang,
>
> Could you rebase the series on the current #ksmbd-for-next-next branch
> to test this ?
> >
> > Use the page-backed payload path for plain TCP SMB2 READ requests of at
> > least 64 KiB.
> Could you provide benchmark results ? and requests smaller than 64 KiB
> are unlikely to benefit from this optimization? Theoretically, it
> seems like it would improve, but I am wondering how much performance
> improvement there actually is.
>
Hi Namjae,
Thank you for the review.
I rebased the series on the current ksmbd-for-next-next branch. Functional
smoke testing passed.
---
Environment: one QEMU guest; ksmbd and the SMB client communicate over
loopback CIFS; warm 4 GiB regular file; SMB 3.1.1; cache=none; one
synchronous fio read job; 5 s ramp; 60 s runtime.
request sizes: 4, 16, 32, 64, 128, 256 KiB, 1 MiB
thresholds: baseline, 4, 16, 32, 64, 128, 256 KiB
formal matrix: 12 matched blocks, 252 valid selected cells
control cells: 65 single-sample threshold/path-validation cells
total: 317 accepted observations; no valid selected cell discarded
The formal results below use whole-block paired bootstrap 95% confidence
intervals and Holm correction. BW, mean clat, and P99 clat are ratios to the
baseline; a BW ratio above 1 is better, and latency ratios below 1 are better.
## RFC 64 KiB threshold versus baseline
request BW ratio [95% CI] Holm p mean clat P99 clat
64 KiB 1.262 [1.217, 1.304] +26.2% 0.00537 0.790 -21.0% 0.780 -22.0%
128 KiB 1.317 [1.274, 1.357] +31.7% 0.00537 0.757 -24.3% 0.730 -27.0%
256 KiB 1.450 [1.424, 1.478] +45.0% 0.00537 0.688 -31.2% 0.591 -40.9%
1 MiB 1.403 [1.315, 1.496] +40.3% 0.00537 0.712 -28.8% 0.778 -22.2%
## Experimental 4 KiB threshold versus baseline
This forces the same splice path for the sub-64 KiB requests.
request BW ratio [95% CI] Holm p mean clat P99 clat result
4 KiB 1.052 [1.031, 1.074] +5.2% 0.01270 0.950 -5.0% 0.948 -5.2% ambiguous
16 KiB 1.135 [1.095, 1.174] +13.5% 0.01270 0.878 -12.2% 0.883 -11.7% beneficial
32 KiB 1.172 [1.151, 1.193] +17.2% 0.00537 0.851 -14.9% 0.842 -15.8% beneficial
64 KiB 1.277 [1.245, 1.306] +27.7% 0.00537 0.781 -21.9% 0.774 -22.6% beneficial
128 KiB 1.335 [1.289, 1.378] +33.5% 0.00537 0.746 -25.4% 0.724 -27.6% beneficial
256 KiB 1.440 [1.398, 1.485] +44.0% 0.00537 0.693 -30.7% 0.602 -39.8% beneficial
1 MiB 1.415 [1.343, 1.487] +41.5% 0.00537 0.706 -29.4% 0.765 -23.5% beneficial
## Same-splice control: RFC 64 KiB threshold versus 4 KiB threshold
request BW ratio [95% CI] result
64 KiB 0.989 [0.965, 1.012] equivalent
128 KiB 0.987 [0.969, 1.004] equivalent
256 KiB 1.007 [0.984, 1.028] equivalent
1 MiB 0.991 [0.941, 1.042] ambiguous
## Threshold-boundary controls
Each entry is `path / BW change versus the same-run baseline`. L is the legacy
path and S is the splice path. These are single-sample controls, not inputs to
the formal statistical result.
threshold 4 KiB 16 KiB 32 KiB 64 KiB 128 KiB 256 KiB
4 KiB S/+4.95% S/+13.78% S/+21.14% S/+26.40% S/+35.47% -
16 KiB L/+2.65% S/+11.84% S/+20.20% S/+26.42% S/+38.27% -
32 KiB L/-1.86% L/-0.53% S/+20.28% S/+27.48% S/+44.22% -
64 KiB L/+4.67% L/+2.54% L/+3.52% S/+32.22% S/+44.22% -
128 KiB L/+2.71% L/+6.43% L/+0.73% L/+3.89% S/+41.74% -
256 KiB L/+3.65% L/+13.00% L/+4.18% L/+8.38% L/-6.54% S/+31.55%
The ftrace path oracle matched every threshold boundary: below the threshold,
ksmbd_vfs_read handled the request; at and above the threshold,
ksmbd_vfs_read_payload and ksmbd_tcp_write_read_payload handled it.
The 4 KiB result is not sufficient for a threshold decision: its confidence
interval lower bound is +3.1%, below the predeclared +5% benefit criterion.
The data support splice-path benefit from 16 KiB upward in this environment.
I will keep 64 KiB as the conservative initial threshold for this RFC. It has
strong results and avoids the inconclusive 4 KiB case. The 16 KiB threshold is
a reasonable follow-up candidate.
Best regards,
Wang Zhaolong
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-17 12:50 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13 7:11 [RFC 0/3] ksmbd: use splice for SMB2 READ responses wang zhaolong
2026-07-13 7:11 ` [RFC 1/3] ksmbd: add read payload infrastructure wang zhaolong
2026-07-13 7:11 ` [RFC 2/3] ksmbd: add splice-based read payload helper wang zhaolong
2026-07-13 7:11 ` [RFC 3/3] ksmbd: use splice payloads for simple SMB2 READ wang zhaolong
2026-07-13 8:58 ` Namjae Jeon
2026-07-13 9:36 ` Wang Zhaolong
2026-07-17 12:49 ` Wang Zhaolong
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox