diff options
Diffstat (limited to 'drivers/net/ethernet/sun/sunvnet.c')
| -rw-r--r-- | drivers/net/ethernet/sun/sunvnet.c | 45 | 
1 files changed, 40 insertions, 5 deletions
diff --git a/drivers/net/ethernet/sun/sunvnet.c b/drivers/net/ethernet/sun/sunvnet.c index d813bfb1a847..f67539650c38 100644 --- a/drivers/net/ethernet/sun/sunvnet.c +++ b/drivers/net/ethernet/sun/sunvnet.c @@ -32,6 +32,11 @@ MODULE_DESCRIPTION("Sun LDOM virtual network driver");  MODULE_LICENSE("GPL");  MODULE_VERSION(DRV_MODULE_VERSION); +/* Heuristic for the number of times to exponentially backoff and + * retry sending an LDC trigger when EAGAIN is encountered + */ +#define	VNET_MAX_RETRIES	10 +  /* Ordered from largest major to lowest */  static struct vio_version vnet_versions[] = {  	{ .major = 1, .minor = 0 }, @@ -260,6 +265,7 @@ static int vnet_send_ack(struct vnet_port *port, struct vio_dring_state *dr,  		.state			= vio_dring_state,  	};  	int err, delay; +	int retries = 0;  	hdr.seq = dr->snd_nxt;  	delay = 1; @@ -272,6 +278,13 @@ static int vnet_send_ack(struct vnet_port *port, struct vio_dring_state *dr,  		udelay(delay);  		if ((delay <<= 1) > 128)  			delay = 128; +		if (retries++ > VNET_MAX_RETRIES) { +			pr_info("ECONNRESET %x:%x:%x:%x:%x:%x\n", +				port->raddr[0], port->raddr[1], +				port->raddr[2], port->raddr[3], +				port->raddr[4], port->raddr[5]); +			err = -ECONNRESET; +		}  	} while (err == -EAGAIN);  	return err; @@ -337,14 +350,17 @@ static int vnet_walk_rx_one(struct vnet_port *port,  	if (IS_ERR(desc))  		return PTR_ERR(desc); +	if (desc->hdr.state != VIO_DESC_READY) +		return 1; + +	rmb(); +  	viodbg(DATA, "vio_walk_rx_one desc[%02x:%02x:%08x:%08x:%llx:%llx]\n",  	       desc->hdr.state, desc->hdr.ack,  	       desc->size, desc->ncookies,  	       desc->cookies[0].cookie_addr,  	       desc->cookies[0].cookie_size); -	if (desc->hdr.state != VIO_DESC_READY) -		return 1;  	err = vnet_rx_one(port, desc->size, desc->cookies, desc->ncookies);  	if (err == -ECONNRESET)  		return err; @@ -475,8 +491,9 @@ static int handle_mcast(struct vnet_port *port, void *msgbuf)  	return 0;  } -static void maybe_tx_wakeup(struct vnet *vp) +static void maybe_tx_wakeup(unsigned long param)  { +	struct vnet *vp = (struct vnet *)param;  	struct net_device *dev = vp->dev;  	netif_tx_lock(dev); @@ -573,8 +590,13 @@ static void vnet_event(void *arg, int event)  			break;  	}  	spin_unlock(&vio->lock); +	/* Kick off a tasklet to wake the queue.  We cannot call +	 * maybe_tx_wakeup directly here because we could deadlock on +	 * netif_tx_lock() with dev_watchdog() +	 */  	if (unlikely(tx_wakeup && err != -ECONNRESET)) -		maybe_tx_wakeup(port->vp); +		tasklet_schedule(&port->vp->vnet_tx_wakeup); +  	local_irq_restore(flags);  } @@ -593,6 +615,7 @@ static int __vnet_tx_trigger(struct vnet_port *port)  		.end_idx		= (u32) -1,  	};  	int err, delay; +	int retries = 0;  	hdr.seq = dr->snd_nxt;  	delay = 1; @@ -605,6 +628,8 @@ static int __vnet_tx_trigger(struct vnet_port *port)  		udelay(delay);  		if ((delay <<= 1) > 128)  			delay = 128; +		if (retries++ > VNET_MAX_RETRIES) +			break;  	} while (err == -EAGAIN);  	return err; @@ -691,7 +716,15 @@ static int vnet_start_xmit(struct sk_buff *skb, struct net_device *dev)  		memset(tx_buf+VNET_PACKET_SKIP+skb->len, 0, len - skb->len);  	} -	d->hdr.ack = VIO_ACK_ENABLE; +	/* We don't rely on the ACKs to free the skb in vnet_start_xmit(), +	 * thus it is safe to not set VIO_ACK_ENABLE for each transmission: +	 * the protocol itself does not require it as long as the peer +	 * sends a VIO_SUBTYPE_ACK for VIO_DRING_STOPPED. +	 * +	 * An ACK for every packet in the ring is expensive as the +	 * sending of LDC messages is slow and affects performance. +	 */ +	d->hdr.ack = VIO_ACK_DISABLE;  	d->size = len;  	d->ncookies = port->tx_bufs[dr->prod].ncookies;  	for (i = 0; i < d->ncookies; i++) @@ -1046,6 +1079,7 @@ static struct vnet *vnet_new(const u64 *local_mac)  	vp = netdev_priv(dev);  	spin_lock_init(&vp->lock); +	tasklet_init(&vp->vnet_tx_wakeup, maybe_tx_wakeup, (unsigned long)vp);  	vp->dev = dev;  	INIT_LIST_HEAD(&vp->port_list); @@ -1105,6 +1139,7 @@ static void vnet_cleanup(void)  		vp = list_first_entry(&vnet_list, struct vnet, list);  		list_del(&vp->list);  		dev = vp->dev; +		tasklet_kill(&vp->vnet_tx_wakeup);  		/* vio_unregister_driver() should have cleaned up port_list */  		BUG_ON(!list_empty(&vp->port_list));  		unregister_netdev(dev);  |