diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
index d10244af5bd2..d492c7716128 100644
--- a/net/smc/af_smc.c
+++ b/net/smc/af_smc.c
@@ -2774,7 +2774,7 @@ int smc_accept(struct socket *sock, struct socket *new_sock,
if (skb_queue_empty(&clcsk->sk_receive_queue))
sk_wait_data(clcsk, &timeo, NULL);
release_sock(clcsk);
- } else if (!atomic_read(&smc_sk(nsk)->conn.bytes_to_rcv)) {
+ } else if (!smc_rx_data_available(&smc_sk(nsk)->conn, 0)) {
lock_sock(nsk);
smc_rx_wait(smc_sk(nsk), &timeo, 0, smc_rx_data_available);
release_sock(nsk);@@ -2939,7 +2939,7 @@ __poll_t smc_poll(struct file *file, struct socket *sock,
mask |= EPOLLOUT | EPOLLWRNORM;
}
}
- if (atomic_read(&smc->conn.bytes_to_rcv))
+ if (smc_rx_data_available(&smc->conn, 0))
mask |= EPOLLIN | EPOLLRDNORM;
if (sk->sk_shutdown & RCV_SHUTDOWN)
mask |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP;
@@ -3237,7 +3237,7 @@ int smc_ioctl(struct socket *sock, unsigned int cmd,
smc->sk.sk_state == SMC_CLOSED)
answ = 0;
else
- answ = atomic_read(&smc->conn.bytes_to_rcv);
+ answ = smc_rx_data_available(&smc->conn, 0);
break;
case SIOCOUTQ:
/* output queue size (not send + not acked) */
diff --git a/net/smc/smc.h b/net/smc/smc.h
index 427b6d63b993..d4325a0a8fd8 100644
--- a/net/smc/smc.h
+++ b/net/smc/smc.h
@@ -203,6 +203,9 @@ struct smc_connection {
* rmbe
*/
int rtoken_idx; /* idx to peer RMB rkey/addr */
+ int rmb_len; /* rmb_desc->len, still valid
+ * after rmb_desc is freed
+ */
struct smc_buf_desc *sndbuf_desc; /* send buffer descriptor */
struct smc_buf_desc *rmb_desc; /* RMBE descriptor */diff --git a/net/smc/smc_cdc.c b/net/smc/smc_cdc.c
index 32d6d03df321..e2c765ff2ba3 100644
--- a/net/smc/smc_cdc.c
+++ b/net/smc/smc_cdc.c
@@ -381,7 +381,7 @@ static void smc_cdc_msg_recv_action(struct smc_sock *smc,
/* bytes_to_rcv is decreased in smc_recvmsg */
smp_mb__before_atomic();
atomic_add(diff_prod, &conn->bytes_to_rcv);
- /* guarantee 0 <= bytes_to_rcv <= rmb_desc->len */
+ /* can be out of range, see smc_rx_data_available() */
smp_mb__after_atomic();
smc->sk.sk_data_ready(&smc->sk);
} else {diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
index 6021322253e9..fe6e4a2f7935 100644
--- a/net/smc/smc_core.c
+++ b/net/smc/smc_core.c
@@ -2505,6 +2505,7 @@ static int __smc_buf_create(struct smc_sock *smc, bool is_smcd, bool is_rmb)
if (is_rmb) {
conn->rmb_desc = buf_desc;
+ conn->rmb_len = buf_desc->len;
conn->rmbe_size_comp = bufsize_comp;
smc->sk.sk_rcvbuf = bufsize * 2;
atomic_set(&conn->bytes_to_rcv, 0);diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c
index 197fddc6271f..85654d9f321b 100644
--- a/net/smc/smc_rx.c
+++ b/net/smc/smc_rx.c
@@ -118,15 +118,20 @@ static void smc_rx_pipe_buf_release(struct pipe_inode_info *pipe,
struct smc_connection *conn = &priv->smc->conn;
struct smc_sock *smc = priv->smc;
struct sock *sk = &smc->sk;
+ bool drained;
lock_sock(sk);
if (conn->freed) {
release_sock(sk);
goto out;
}
+ /* drop the released bytes from splice_pending before cons moves
+ * past them, so no reader counts them twice
+ */
+ drained = atomic_sub_and_test(priv->len, &conn->splice_pending);
smc_rx_update_cons(smc, priv->len);
release_sock(sk);
- if (atomic_sub_and_test(priv->len, &conn->splice_pending))
+ if (drained)
smc_rx_wake_up(sk);
out:
kfree(priv);@@ -506,7 +511,7 @@ int smc_rx_recvmsg(struct smc_sock *smc, struct msghdr *msg,
/* increased in recv tasklet smc_cdc_msg_rcv() */
smp_mb__before_atomic();
atomic_sub(copylen, &conn->bytes_to_rcv);
- /* guarantee 0 <= bytes_to_rcv <= rmb_desc->len */
+ /* can be out of range, see smc_rx_data_available() */
smp_mb__after_atomic();
if (msg && smc_rx_update_consumer(smc, cons, copylen))
goto out;
diff --git a/net/smc/smc_rx.h b/net/smc/smc_rx.h
index 994f5e42d1ba..6bf44e3cc33a 100644
--- a/net/smc/smc_rx.h
+++ b/net/smc/smc_rx.h
@@ -25,7 +25,16 @@ int smc_rx_wait(struct smc_sock *smc, long *timeo, size_t peeked,
int (*fcrit)(struct smc_connection *conn, size_t baseline));
static inline int smc_rx_data_available(struct smc_connection *conn, size_t peeked)
{
- return atomic_read(&conn->bytes_to_rcv) - peeked;
+ int avail = atomic_read(&conn->bytes_to_rcv);
+
+ /* bytes_to_rcv follows the peer's producer cursor and can be past
+ * the RMB or negative. Report no more than fits in the RMB behind the
+ * bytes already spliced or peeked. Compare before subtracting: near
+ * INT_MIN, avail - peeked wraps. This also runs unlocked, possibly
+ * after rmb_desc was freed, hence rmb_len.
+ */
+ avail = min(avail, conn->rmb_len - atomic_read(&conn->splice_pending));
+ return avail > (int)peeked ? avail - (int)peeked : 0;
}
#endif /* SMC_RX_H */
--
2.56.0