An RX batch can make a socket readable while the worker still owns its socket lock. Waking a reader at that point can make it immediately wait for the same lock.
For the callback installed by sock_init_data(), record when the existing low-watermark condition is met and issue one notification after the batch releases the lock. Save the initial sock_def_readable() callback when the AF_VSOCK socket is created because virtio_transport_common can be built as a module and cannot refer to that unexported symbol directly. When sk_data_ready or sk_write_space no longer matches that initial callback, pass queued skbs to the current callback one skb at a time. The handoff stops if the callback is the default again or the queue does not shrink. After 64 successful callbacks, queue another bounded handoff if data remains. vsock_bpf_update_proto() schedules the same handoff so an attach or detach outside the worker still drains skbs already queued. Always deliver a coalesced write-space event to the saved default callback before sampling the callback state. A current replacement is invoked by the handoff as well, so a callback change cannot hide the native writer wakeup or pending sockmap work. Sampling at batch finish takes sk_callback_lock once. The per-packet fast path still uses READ_ONCE() and does not take that lock. Control packets, state transitions, transport changes and non-batched sockets keep their existing behavior. Signed-off-by: Jia Jia <[email protected]> --- include/linux/virtio_vsock.h | 1 + include/net/af_vsock.h | 8 +- net/vmw_vsock/af_vsock.c | 103 ++++++++++++++++++++++++ net/vmw_vsock/virtio_transport_common.c | 73 ++++++++++++++--- net/vmw_vsock/vsock_bpf.c | 2 + 5 files changed, 174 insertions(+), 13 deletions(-) diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h index 3a58120d9078..21a3460b940f 100644 --- a/include/linux/virtio_vsock.h +++ b/include/linux/virtio_vsock.h @@ -291,6 +291,7 @@ struct virtio_transport_rx_batch { struct sockaddr_vm src; struct sockaddr_vm dst; bool write_space_pending; + bool data_ready_pending; }; void virtio_transport_recv_pkt_batch(struct virtio_transport *t, diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 9d8ae62209ed..86d6e6ba3d9d 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -63,8 +63,11 @@ struct vsock_sock { u32 peer_shutdown; bool sent_request; bool ignore_connecting_rst; - /* Initial callback, used to identify replacements. */ + /* Initial callbacks, used to identify replacements. */ + void (*default_data_ready)(struct sock *sk); void (*default_write_space)(struct sock *sk); + struct delayed_work rx_cb_work; + bool rx_cb_retried; /* Protected by lock_sock(sk) */ u64 buffer_size; @@ -80,6 +83,9 @@ s64 vsock_stream_has_data(struct vsock_sock *vsk); s64 vsock_stream_has_space(struct vsock_sock *vsk); struct sock *vsock_create_connected(struct sock *parent); void vsock_data_ready(struct sock *sk); +bool vsock_rx_cb_is_native(struct sock *sk); +void vsock_rx_callback_kick(struct sock *sk); +void vsock_rx_callback_handoff(struct sock *sk); /**** TRANSPORT ****/ diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index fe7f49da6c4b..df6faee0bd37 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr) return retval; } +#define VSOCK_RX_CB_HANDOFF_MAX 64 + +static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay) +{ + struct vsock_sock *vsk = vsock_sk(sk); + + sock_hold(sk); + if (!schedule_delayed_work(&vsk->rx_cb_work, delay)) + sock_put(sk); +} + +void vsock_rx_callback_kick(struct sock *sk) +{ + vsock_rx_callback_queue(sk, 0); +} + +/* Caller holds sk_callback_lock. */ +bool vsock_rx_cb_is_native(struct sock *sk) +{ + struct vsock_sock *vsk = vsock_sk(sk); + + return READ_ONCE(sk->sk_prot) == sk->sk_prot_creator && + READ_ONCE(sk->sk_data_ready) == vsk->default_data_ready && + READ_ONCE(sk->sk_write_space) == vsk->default_write_space; +} +EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native); + +/* Caller holds lock_sock(). */ +void vsock_rx_callback_handoff(struct sock *sk) +{ + struct vsock_sock *vsk = vsock_sk(sk); + void (*write_space)(struct sock *sk); + void (*data_ready)(struct sock *sk); + bool wrote = false; + s64 before, after; + int i; + + if (sock_flag(sk, SOCK_DEAD) || !vsk->transport) + return; + + read_lock_bh(&sk->sk_callback_lock); + data_ready = READ_ONCE(sk->sk_data_ready); + if (READ_ONCE(sk->sk_prot) != sk->sk_prot_creator && + data_ready == vsk->default_data_ready) { + read_unlock_bh(&sk->sk_callback_lock); + /* + * start_verdict publishes sk_data_ready after the proto swap. + * Retry once before treating this as a stable non-RX setup. + */ + if (!vsk->rx_cb_retried) { + vsk->rx_cb_retried = true; + vsock_rx_callback_queue(sk, 1); + return; + } + vsk->rx_cb_retried = false; + data_ready(sk); + return; + } + read_unlock_bh(&sk->sk_callback_lock); + vsk->rx_cb_retried = false; + + for (i = 0; i < VSOCK_RX_CB_HANDOFF_MAX; i++) { + read_lock_bh(&sk->sk_callback_lock); + data_ready = READ_ONCE(sk->sk_data_ready); + write_space = READ_ONCE(sk->sk_write_space); + read_unlock_bh(&sk->sk_callback_lock); + + if (!wrote && write_space != vsk->default_write_space) { + write_space(sk); + wrote = true; + } + if (data_ready == vsk->default_data_ready) { + data_ready(sk); + break; + } + before = vsock_stream_has_data(vsk); + if (before <= 0) + break; + data_ready(sk); + after = vsock_stream_has_data(vsk); + if (after >= before) + break; + if (i == VSOCK_RX_CB_HANDOFF_MAX - 1 && after > 0) + vsock_rx_callback_queue(sk, 0); + } +} +EXPORT_SYMBOL_GPL(vsock_rx_callback_handoff); + +static void vsock_rx_callback_work(struct work_struct *work) +{ + struct vsock_sock *vsk = container_of(work, struct vsock_sock, + rx_cb_work.work); + struct sock *sk = sk_vsock(vsk); + + lock_sock(sk); + if (!sock_flag(sk, SOCK_DEAD)) + vsock_rx_callback_handoff(sk); + release_sock(sk); + sock_put(sk); +} + static void vsock_connect_timeout(struct work_struct *work); static struct sock *__vsock_create(struct net *net, @@ -958,6 +1059,7 @@ static struct sock *__vsock_create(struct net *net, sk->sk_type = type; vsk = vsock_sk(sk); + vsk->default_data_ready = sk->sk_data_ready; vsk->default_write_space = sk->sk_write_space; vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY); vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY); @@ -976,6 +1078,7 @@ static struct sock *__vsock_create(struct net *net, WRITE_ONCE(vsk->peer_shutdown, 0); INIT_DELAYED_WORK(&vsk->connect_work, vsock_connect_timeout); INIT_DELAYED_WORK(&vsk->pending_work, vsock_pending_work); + INIT_DELAYED_WORK(&vsk->rx_cb_work, vsock_rx_callback_work); psk = parent ? vsock_sk(parent) : NULL; if (parent) { diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index f0398ae2200d..5499cd1d8a25 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1583,10 +1583,12 @@ out: static int virtio_transport_recv_connected(struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + bool *data_ready_pending) { struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb); struct vsock_sock *vsk = vsock_sk(sk); + void (*data_ready)(struct sock *sk); int err = 0; switch (le16_to_cpu(hdr->op)) { @@ -1602,7 +1604,23 @@ virtio_transport_recv_connected(struct sock *sk, vsock_remove_sock(vsk); break; } - vsock_data_ready(sk); + if (!data_ready_pending) { + vsock_data_ready(sk); + } else { + data_ready = READ_ONCE(sk->sk_data_ready); + if (data_ready == vsk->default_data_ready) { + if (!*data_ready_pending && + (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat || + sock_flag(sk, SOCK_DONE))) + *data_ready_pending = true; + } else { + /* Use the callback seen for this packet. */ + *data_ready_pending = false; + if (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat || + sock_flag(sk, SOCK_DONE)) + data_ready(sk); + } + } return err; case VIRTIO_VSOCK_OP_CREDIT_REQUEST: virtio_transport_send_credit_update(vsk); @@ -1836,6 +1854,7 @@ struct virtio_transport_rx_pkt_ctx { const struct sockaddr_vm *dst; bool *batchable; struct virtio_transport_rx_batch *batch; + bool defer_data_ready; }; static bool @@ -1909,7 +1928,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t, kfree_skb(skb); break; case TCP_ESTABLISHED: - virtio_transport_recv_connected(sk, skb); + virtio_transport_recv_connected(sk, skb, + ctx->defer_data_ready && ctx->batch ? + &ctx->batch->data_ready_pending : NULL); break; case TCP_CLOSING: virtio_transport_recv_disconnecting(sk, skb); @@ -1978,33 +1999,45 @@ EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt); * Finish the RX batch. * For a non-empty batch, the caller must hold the socket lock acquired with * lock_sock(). This function releases the lock and the batch's lookup - * reference. - * An empty batch is a no-op. + * reference. An empty batch is a no-op. */ void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch) { bool write_space_pending = batch->write_space_pending; - void (*write_space)(struct sock *sk); + bool data_ready_pending = batch->data_ready_pending; struct sock *sk = batch->sk; struct vsock_sock *vsk; + bool native; batch->sk = NULL; batch->pkts = 0; batch->bytes = 0; batch->net = NULL; batch->write_space_pending = false; + batch->data_ready_pending = false; if (!sk) return; - if (write_space_pending) { - vsk = vsock_sk(sk); + vsk = vsock_sk(sk); + if (write_space_pending) vsk->default_write_space(sk); - write_space = READ_ONCE(sk->sk_write_space); - if (write_space != vsk->default_write_space) - write_space(sk); + + read_lock_bh(&sk->sk_callback_lock); + native = vsock_rx_cb_is_native(sk); + read_unlock_bh(&sk->sk_callback_lock); + + if (native) { + release_sock(sk); + if (data_ready_pending) + vsk->default_data_ready(sk); + sock_put(sk); + return; } + if (write_space_pending || data_ready_pending) + vsock_rx_callback_handoff(sk); + release_sock(sk); sock_put(sk); } @@ -2015,9 +2048,9 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t, struct virtio_transport_rx_batch *batch) { struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb); + bool batchable, defer_data_ready, start_batch; struct virtio_transport_rx_pkt_ctx ctx; struct sockaddr_vm src, dst; - bool batchable, start_batch; struct sock *sk; bool free_pkt; @@ -2038,6 +2071,17 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t, vsock_addr_equals_addr(&batch->dst, &dst) && virtio_transport_recv_pkt_batchable(t, batch->sk)) { sk = batch->sk; + defer_data_ready = READ_ONCE(sk->sk_data_ready) == + vsock_sk(sk)->default_data_ready; + if (unlikely(batch->data_ready_pending && !defer_data_ready)) { + /* + * BPF map updates can replace callbacks while lock_sock() + * remains held. Finish the old notification first. + */ + virtio_transport_rx_batch_finish(batch); + goto lookup; + } + if (!skb_set_owner_sk_safe(skb, sk)) { WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n"); virtio_transport_rx_batch_finish(batch); @@ -2051,6 +2095,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t, .dst = &dst, .batchable = &batchable, .batch = batch, + .defer_data_ready = defer_data_ready, }; free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx); @@ -2064,6 +2109,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t, virtio_transport_rx_batch_finish(batch); } +lookup: sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net); if (!sk) { virtio_transport_rx_batch_finish(batch); @@ -2089,6 +2135,8 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t, if (start_batch) batch->sk = sk; + defer_data_ready = start_batch && + READ_ONCE(sk->sk_data_ready) == vsock_sk(sk)->default_data_ready; ctx = (struct virtio_transport_rx_pkt_ctx) { .net = net, @@ -2096,6 +2144,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t, .dst = &dst, .batchable = start_batch ? &batchable : NULL, .batch = start_batch ? batch : NULL, + .defer_data_ready = defer_data_ready, }; free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx); if (start_batch && batchable) { diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c index 9049d2648646..7770a6c51dbe 100644 --- a/net/vmw_vsock/vsock_bpf.c +++ b/net/vmw_vsock/vsock_bpf.c @@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore if (restore) { sk->sk_write_space = psock->saved_write_space; sock_replace_proto(sk, psock->sk_proto); + vsock_rx_callback_kick(sk); return 0; } @@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore vsock_bpf_check_needs_rebuild(psock->sk_proto); sock_replace_proto(sk, &vsock_bpf_prot); + vsock_rx_callback_kick(sk); return 0; } -- 2.34.1

