diff options
Diffstat (limited to 'drivers/net/ethernet/arc/emac_main.c')
| -rw-r--r-- | drivers/net/ethernet/arc/emac_main.c | 164 | 
1 files changed, 142 insertions, 22 deletions
| diff --git a/drivers/net/ethernet/arc/emac_main.c b/drivers/net/ethernet/arc/emac_main.c index 3241af1ce718..bd277b0dc615 100644 --- a/drivers/net/ethernet/arc/emac_main.c +++ b/drivers/net/ethernet/arc/emac_main.c @@ -26,6 +26,8 @@  #include "emac.h" +static void arc_emac_restart(struct net_device *ndev); +  /**   * arc_emac_tx_avail - Return the number of available slots in the tx ring.   * @priv: Pointer to ARC EMAC private data structure. @@ -210,39 +212,48 @@ static int arc_emac_rx(struct net_device *ndev, int budget)  			continue;  		} -		pktlen = info & LEN_MASK; -		stats->rx_packets++; -		stats->rx_bytes += pktlen; -		skb = rx_buff->skb; -		skb_put(skb, pktlen); -		skb->dev = ndev; -		skb->protocol = eth_type_trans(skb, ndev); - -		dma_unmap_single(&ndev->dev, dma_unmap_addr(rx_buff, addr), -				 dma_unmap_len(rx_buff, len), DMA_FROM_DEVICE); - -		/* Prepare the BD for next cycle */ -		rx_buff->skb = netdev_alloc_skb_ip_align(ndev, -							 EMAC_BUFFER_SIZE); -		if (unlikely(!rx_buff->skb)) { +		/* Prepare the BD for next cycle. netif_receive_skb() +		 * only if new skb was allocated and mapped to avoid holes +		 * in the RX fifo. +		 */ +		skb = netdev_alloc_skb_ip_align(ndev, EMAC_BUFFER_SIZE); +		if (unlikely(!skb)) { +			if (net_ratelimit()) +				netdev_err(ndev, "cannot allocate skb\n"); +			/* Return ownership to EMAC */ +			rxbd->info = cpu_to_le32(FOR_EMAC | EMAC_BUFFER_SIZE);  			stats->rx_errors++; -			/* Because receive_skb is below, increment rx_dropped */  			stats->rx_dropped++;  			continue;  		} -		/* receive_skb only if new skb was allocated to avoid holes */ -		netif_receive_skb(skb); - -		addr = dma_map_single(&ndev->dev, (void *)rx_buff->skb->data, +		addr = dma_map_single(&ndev->dev, (void *)skb->data,  				      EMAC_BUFFER_SIZE, DMA_FROM_DEVICE);  		if (dma_mapping_error(&ndev->dev, addr)) {  			if (net_ratelimit()) -				netdev_err(ndev, "cannot dma map\n"); -			dev_kfree_skb(rx_buff->skb); +				netdev_err(ndev, "cannot map dma buffer\n"); +			dev_kfree_skb(skb); +			/* Return ownership to EMAC */ +			rxbd->info = cpu_to_le32(FOR_EMAC | EMAC_BUFFER_SIZE);  			stats->rx_errors++; +			stats->rx_dropped++;  			continue;  		} + +		/* unmap previosly mapped skb */ +		dma_unmap_single(&ndev->dev, dma_unmap_addr(rx_buff, addr), +				 dma_unmap_len(rx_buff, len), DMA_FROM_DEVICE); + +		pktlen = info & LEN_MASK; +		stats->rx_packets++; +		stats->rx_bytes += pktlen; +		skb_put(rx_buff->skb, pktlen); +		rx_buff->skb->dev = ndev; +		rx_buff->skb->protocol = eth_type_trans(rx_buff->skb, ndev); + +		netif_receive_skb(rx_buff->skb); + +		rx_buff->skb = skb;  		dma_unmap_addr_set(rx_buff, addr, addr);  		dma_unmap_len_set(rx_buff, len, EMAC_BUFFER_SIZE); @@ -259,6 +270,53 @@ static int arc_emac_rx(struct net_device *ndev, int budget)  }  /** + * arc_emac_rx_miss_handle - handle R_MISS register + * @ndev:	Pointer to the net_device structure. + */ +static void arc_emac_rx_miss_handle(struct net_device *ndev) +{ +	struct arc_emac_priv *priv = netdev_priv(ndev); +	struct net_device_stats *stats = &ndev->stats; +	unsigned int miss; + +	miss = arc_reg_get(priv, R_MISS); +	if (miss) { +		stats->rx_errors += miss; +		stats->rx_missed_errors += miss; +		priv->rx_missed_errors += miss; +	} +} + +/** + * arc_emac_rx_stall_check - check RX stall + * @ndev:	Pointer to the net_device structure. + * @budget:	How many BDs requested to process on 1 call. + * @work_done:	How many BDs processed + * + * Under certain conditions EMAC stop reception of incoming packets and + * continuously increment R_MISS register instead of saving data into + * provided buffer. This function detect that condition and restart + * EMAC. + */ +static void arc_emac_rx_stall_check(struct net_device *ndev, +				    int budget, unsigned int work_done) +{ +	struct arc_emac_priv *priv = netdev_priv(ndev); +	struct arc_emac_bd *rxbd; + +	if (work_done) +		priv->rx_missed_errors = 0; + +	if (priv->rx_missed_errors && budget) { +		rxbd = &priv->rxbd[priv->last_rx_bd]; +		if (le32_to_cpu(rxbd->info) & FOR_EMAC) { +			arc_emac_restart(ndev); +			priv->rx_missed_errors = 0; +		} +	} +} + +/**   * arc_emac_poll - NAPI poll handler.   * @napi:	Pointer to napi_struct structure.   * @budget:	How many BDs to process on 1 call. @@ -272,6 +330,7 @@ static int arc_emac_poll(struct napi_struct *napi, int budget)  	unsigned int work_done;  	arc_emac_tx_clean(ndev); +	arc_emac_rx_miss_handle(ndev);  	work_done = arc_emac_rx(ndev, budget);  	if (work_done < budget) { @@ -279,6 +338,8 @@ static int arc_emac_poll(struct napi_struct *napi, int budget)  		arc_reg_or(priv, R_ENABLE, RXINT_MASK | TXINT_MASK);  	} +	arc_emac_rx_stall_check(ndev, budget, work_done); +  	return work_done;  } @@ -320,6 +381,8 @@ static irqreturn_t arc_emac_intr(int irq, void *dev_instance)  		if (status & MSER_MASK) {  			stats->rx_missed_errors += 0x100;  			stats->rx_errors += 0x100; +			priv->rx_missed_errors += 0x100; +			napi_schedule(&priv->napi);  		}  		if (status & RXCR_MASK) { @@ -732,6 +795,63 @@ static int arc_emac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)  } +/** + * arc_emac_restart - Restart EMAC + * @ndev:	Pointer to net_device structure. + * + * This function do hardware reset of EMAC in order to restore + * network packets reception. + */ +static void arc_emac_restart(struct net_device *ndev) +{ +	struct arc_emac_priv *priv = netdev_priv(ndev); +	struct net_device_stats *stats = &ndev->stats; +	int i; + +	if (net_ratelimit()) +		netdev_warn(ndev, "restarting stalled EMAC\n"); + +	netif_stop_queue(ndev); + +	/* Disable interrupts */ +	arc_reg_clr(priv, R_ENABLE, RXINT_MASK | TXINT_MASK | ERR_MASK); + +	/* Disable EMAC */ +	arc_reg_clr(priv, R_CTRL, EN_MASK); + +	/* Return the sk_buff to system */ +	arc_free_tx_queue(ndev); + +	/* Clean Tx BD's */ +	priv->txbd_curr = 0; +	priv->txbd_dirty = 0; +	memset(priv->txbd, 0, TX_RING_SZ); + +	for (i = 0; i < RX_BD_NUM; i++) { +		struct arc_emac_bd *rxbd = &priv->rxbd[i]; +		unsigned int info = le32_to_cpu(rxbd->info); + +		if (!(info & FOR_EMAC)) { +			stats->rx_errors++; +			stats->rx_dropped++; +		} +		/* Return ownership to EMAC */ +		rxbd->info = cpu_to_le32(FOR_EMAC | EMAC_BUFFER_SIZE); +	} +	priv->last_rx_bd = 0; + +	/* Make sure info is visible to EMAC before enable */ +	wmb(); + +	/* Enable interrupts */ +	arc_reg_set(priv, R_ENABLE, RXINT_MASK | TXINT_MASK | ERR_MASK); + +	/* Enable EMAC */ +	arc_reg_or(priv, R_CTRL, EN_MASK); + +	netif_start_queue(ndev); +} +  static const struct net_device_ops arc_emac_netdev_ops = {  	.ndo_open		= arc_emac_open,  	.ndo_stop		= arc_emac_stop, | 

