From b0cffed54338e19e3cc46c9963478223eee0d560 Mon Sep 17 00:00:00 2001 From: Sowmini Varadhan Date: Sat, 8 Nov 2014 20:41:53 -0500 Subject: [PATCH 1/3] sunvnet: Fix race between vnet_start_xmit() and vnet_ack() When vnet_start_xmit() is concurrent with vnet_ack(), we may have a race that looks like: thread 1 thread 2 vnet_start_xmit vnet_event_napi -> vnet_rx __vnet_tx_trigger for some desc X at this point dr->prod == X peer sends back a stopped ack for X we process X, but X == dr->prod so we bail out in vnet_ack with !idx_is_pending update dr->prod As a result of the fact that we never processed the stopped ack for X, the Tx path is led to incorrectly believe that the peer is still "started" and reading, but the peer has stopped reading, which will ultimately end in flow-control assertions. The fix is to synchronize the above 2 paths on the netif_tx_lock. Signed-off-by: Sowmini Varadhan Signed-off-by: David S. Miller --- drivers/net/ethernet/sun/sunvnet.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/sun/sunvnet.c b/drivers/net/ethernet/sun/sunvnet.c index 5c5fb59adf76..deb395a82e50 100644 --- a/drivers/net/ethernet/sun/sunvnet.c +++ b/drivers/net/ethernet/sun/sunvnet.c @@ -559,15 +559,17 @@ static int vnet_ack(struct vnet_port *port, void *msgbuf) return 0; end = pkt->end_idx; - if (unlikely(!idx_is_pending(dr, end))) - return 0; - vp = port->vp; dev = vp->dev; + netif_tx_lock(dev); + if (unlikely(!idx_is_pending(dr, end))) { + netif_tx_unlock(dev); + return 0; + } + /* sync for race conditions with vnet_start_xmit() and tell xmit it * is time to send a trigger. */ - netif_tx_lock(dev); dr->cons = next_idx(end, dr); desc = vio_dring_entry(dr, dr->cons); if (desc->hdr.state == VIO_DESC_READY && port->start_cons) { From 777362d7215f63318fc4a6fae9b320328d92e9f6 Mon Sep 17 00:00:00 2001 From: Sowmini Varadhan Date: Sat, 8 Nov 2014 20:42:10 -0500 Subject: [PATCH 2/3] sunvnet: vnet_ack() should check if !start_cons to send a missed trigger As per comments in vnet_start_xmit, for the edge case when outgoing vnet_start_xmit() data and an incoming STOPPED ACK cross each other in flight, we may need to send the missed START trigger from maybe_tx_wakeup() after checking for a false value of start_cons Signed-off-by: Sowmini Varadhan Signed-off-by: David S. Miller --- drivers/net/ethernet/sun/sunvnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/sun/sunvnet.c b/drivers/net/ethernet/sun/sunvnet.c index deb395a82e50..826b3852c7df 100644 --- a/drivers/net/ethernet/sun/sunvnet.c +++ b/drivers/net/ethernet/sun/sunvnet.c @@ -572,7 +572,7 @@ static int vnet_ack(struct vnet_port *port, void *msgbuf) */ dr->cons = next_idx(end, dr); desc = vio_dring_entry(dr, dr->cons); - if (desc->hdr.state == VIO_DESC_READY && port->start_cons) { + if (desc->hdr.state == VIO_DESC_READY && !port->start_cons) { /* vnet_start_xmit() just populated this dring but missed * sending the "start" LDC message to the consumer. * Send a "start" trigger on its behalf. From df20286ab1e36eaaf1f6c7e5e2c56bea1ffc26c0 Mon Sep 17 00:00:00 2001 From: Sowmini Varadhan Date: Sat, 8 Nov 2014 20:42:20 -0500 Subject: [PATCH 3/3] sunvnet: Add missing rcu_read_unlock() in vnet_start_xmit The out_dropped label will only do rcu_read_unlock for non-null port. So add the missing rcu_read_unlock() when bailing due to non-null port. Signed-off-by: Sowmini Varadhan Signed-off-by: David S. Miller --- drivers/net/ethernet/sun/sunvnet.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/sun/sunvnet.c b/drivers/net/ethernet/sun/sunvnet.c index 826b3852c7df..55d66c9a6627 100644 --- a/drivers/net/ethernet/sun/sunvnet.c +++ b/drivers/net/ethernet/sun/sunvnet.c @@ -981,8 +981,10 @@ static int vnet_start_xmit(struct sk_buff *skb, struct net_device *dev) rcu_read_lock(); port = __tx_port_find(vp, skb); - if (unlikely(!port)) + if (unlikely(!port)) { + rcu_read_unlock(); goto out_dropped; + } if (skb->len > port->rmtu) { unsigned long localmtu = port->rmtu - ETH_HLEN;