Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b7bde6d authored by Nimrod Andy's avatar Nimrod Andy Committed by David S. Miller
Browse files

net: fec: implement rx_copybreak to improve rx performance



- Copy short frames and keep the buffers mapped, re-allocate skb instead of
  memory copy for long frames.
- Add support for setting/getting rx_copybreak using generic ethtool tunable

Changes V3:
* As Eric Dumazet's suggestion that removing the copybreak module parameter
  and only keep the ethtool API support for rx_copybreak.

Changes V2:
* Implements rx_copybreak
* Rx_copybreak provides module parameter to change this value
* Add tunable_ops support for rx_copybreak

Signed-off-by: default avatarFugang Duan <B38611@freescale.com>
Signed-off-by: default avatarFrank Li <Frank.Li@freescale.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ce1a4ea3
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -482,6 +482,8 @@ struct fec_enet_private {
	unsigned int tx_pkts_itr;
	unsigned int tx_pkts_itr;
	unsigned int tx_time_itr;
	unsigned int tx_time_itr;
	unsigned int itr_clk_rate;
	unsigned int itr_clk_rate;

	u32 rx_copybreak;
};
};


void fec_ptp_init(struct platform_device *pdev);
void fec_ptp_init(struct platform_device *pdev);
+143 −65
Original line number Original line Diff line number Diff line
@@ -236,6 +236,8 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
#define FEC_PAUSE_FLAG_AUTONEG	0x1
#define FEC_PAUSE_FLAG_AUTONEG	0x1
#define FEC_PAUSE_FLAG_ENABLE	0x2
#define FEC_PAUSE_FLAG_ENABLE	0x2


#define COPYBREAK_DEFAULT	256

#define TSO_HEADER_SIZE		128
#define TSO_HEADER_SIZE		128
/* Max number of allowed TCP segments for software TSO */
/* Max number of allowed TCP segments for software TSO */
#define FEC_MAX_TSO_SEGS	100
#define FEC_MAX_TSO_SEGS	100
@@ -1322,6 +1324,50 @@ fec_enet_tx(struct net_device *ndev)
	return;
	return;
}
}


static int
fec_enet_new_rxbdp(struct net_device *ndev, struct bufdesc *bdp, struct sk_buff *skb)
{
	struct  fec_enet_private *fep = netdev_priv(ndev);
	int off;

	off = ((unsigned long)skb->data) & fep->rx_align;
	if (off)
		skb_reserve(skb, fep->rx_align + 1 - off);

	bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, skb->data,
					  FEC_ENET_RX_FRSIZE - fep->rx_align,
					  DMA_FROM_DEVICE);
	if (dma_mapping_error(&fep->pdev->dev, bdp->cbd_bufaddr)) {
		if (net_ratelimit())
			netdev_err(ndev, "Rx DMA memory map failed\n");
		return -ENOMEM;
	}

	return 0;
}

static bool fec_enet_copybreak(struct net_device *ndev, struct sk_buff **skb,
			       struct bufdesc *bdp, u32 length)
{
	struct  fec_enet_private *fep = netdev_priv(ndev);
	struct sk_buff *new_skb;

	if (length > fep->rx_copybreak)
		return false;

	new_skb = netdev_alloc_skb(ndev, length);
	if (!new_skb)
		return false;

	dma_sync_single_for_cpu(&fep->pdev->dev, bdp->cbd_bufaddr,
				FEC_ENET_RX_FRSIZE - fep->rx_align,
				DMA_FROM_DEVICE);
	memcpy(new_skb->data, (*skb)->data, length);
	*skb = new_skb;

	return true;
}

/* During a receive, the cur_rx points to the current incoming buffer.
/* During a receive, the cur_rx points to the current incoming buffer.
 * When we update through the ring, if the next incoming buffer has
 * When we update through the ring, if the next incoming buffer has
 * not been given to the system, we just set the empty indicator,
 * not been given to the system, we just set the empty indicator,
@@ -1336,6 +1382,7 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id)
	struct fec_enet_priv_rx_q *rxq;
	struct fec_enet_priv_rx_q *rxq;
	struct bufdesc *bdp;
	struct bufdesc *bdp;
	unsigned short status;
	unsigned short status;
	struct  sk_buff *skb_new = NULL;
	struct  sk_buff *skb;
	struct  sk_buff *skb;
	ushort	pkt_len;
	ushort	pkt_len;
	__u8 *data;
	__u8 *data;
@@ -1344,6 +1391,7 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id)
	bool	vlan_packet_rcvd = false;
	bool	vlan_packet_rcvd = false;
	u16	vlan_tag;
	u16	vlan_tag;
	int	index = 0;
	int	index = 0;
	bool	is_copybreak;


#ifdef CONFIG_M532x
#ifdef CONFIG_M532x
	flush_cache_all();
	flush_cache_all();
@@ -1401,11 +1449,27 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id)
		ndev->stats.rx_bytes += pkt_len;
		ndev->stats.rx_bytes += pkt_len;


		index = fec_enet_get_bd_index(rxq->rx_bd_base, bdp, fep);
		index = fec_enet_get_bd_index(rxq->rx_bd_base, bdp, fep);
		data = rxq->rx_skbuff[index]->data;
		skb = rxq->rx_skbuff[index];
		dma_sync_single_for_cpu(&fep->pdev->dev, bdp->cbd_bufaddr,

		/* The packet length includes FCS, but we don't want to
		 * include that when passing upstream as it messes up
		 * bridging applications.
		 */
		is_copybreak = fec_enet_copybreak(ndev, &skb, bdp, pkt_len - 4);
		if (!is_copybreak) {
			skb_new = netdev_alloc_skb(ndev, FEC_ENET_RX_FRSIZE);
			if (unlikely(!skb_new)) {
				ndev->stats.rx_dropped++;
				goto rx_processing_done;
			}
			dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr,
					 FEC_ENET_RX_FRSIZE - fep->rx_align,
					 FEC_ENET_RX_FRSIZE - fep->rx_align,
					 DMA_FROM_DEVICE);
					 DMA_FROM_DEVICE);
		}


		prefetch(skb->data - NET_IP_ALIGN);
		skb_put(skb, pkt_len - 4);
		data = skb->data;
		if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
		if (id_entry->driver_data & FEC_QUIRK_SWAP_FRAME)
			swap_buffer(data, pkt_len);
			swap_buffer(data, pkt_len);


@@ -1422,32 +1486,13 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id)
			struct vlan_hdr *vlan_header =
			struct vlan_hdr *vlan_header =
					(struct vlan_hdr *) (data + ETH_HLEN);
					(struct vlan_hdr *) (data + ETH_HLEN);
			vlan_tag = ntohs(vlan_header->h_vlan_TCI);
			vlan_tag = ntohs(vlan_header->h_vlan_TCI);
			pkt_len -= VLAN_HLEN;


			vlan_packet_rcvd = true;
			vlan_packet_rcvd = true;
		}

		/* This does 16 byte alignment, exactly what we need.
		 * The packet length includes FCS, but we don't want to
		 * include that when passing upstream as it messes up
		 * bridging applications.
		 */
		skb = netdev_alloc_skb(ndev, pkt_len - 4 + NET_IP_ALIGN);

		if (unlikely(!skb)) {
			ndev->stats.rx_dropped++;
		} else {
			int payload_offset = (2 * ETH_ALEN);
			skb_reserve(skb, NET_IP_ALIGN);
			skb_put(skb, pkt_len - 4);	/* Make room */


			/* Extract the frame data without the VLAN header. */
			skb_copy_to_linear_data_offset(skb, VLAN_HLEN,
			skb_copy_to_linear_data(skb, data, (2 * ETH_ALEN));
						       data, (2 * ETH_ALEN));
			if (vlan_packet_rcvd)
			skb_pull(skb, VLAN_HLEN);
				payload_offset = (2 * ETH_ALEN) + VLAN_HLEN;
		}
			skb_copy_to_linear_data_offset(skb, (2 * ETH_ALEN),
						       data + payload_offset,
						       pkt_len - 4 - (2 * ETH_ALEN));


		skb->protocol = eth_type_trans(skb, ndev);
		skb->protocol = eth_type_trans(skb, ndev);


@@ -1473,11 +1518,16 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id)
					       vlan_tag);
					       vlan_tag);


		napi_gro_receive(&fep->napi, skb);
		napi_gro_receive(&fep->napi, skb);
		}


		if (is_copybreak) {
			dma_sync_single_for_device(&fep->pdev->dev, bdp->cbd_bufaddr,
			dma_sync_single_for_device(&fep->pdev->dev, bdp->cbd_bufaddr,
						   FEC_ENET_RX_FRSIZE - fep->rx_align,
						   FEC_ENET_RX_FRSIZE - fep->rx_align,
						   DMA_FROM_DEVICE);
						   DMA_FROM_DEVICE);
		} else {
			rxq->rx_skbuff[index] = skb_new;
			fec_enet_new_rxbdp(ndev, bdp, skb_new);
		}

rx_processing_done:
rx_processing_done:
		/* Clear the status flags for this buffer */
		/* Clear the status flags for this buffer */
		status &= ~BD_ENET_RX_STATS;
		status &= ~BD_ENET_RX_STATS;
@@ -2392,6 +2442,44 @@ static void fec_enet_itr_coal_init(struct net_device *ndev)
	fec_enet_set_coalesce(ndev, &ec);
	fec_enet_set_coalesce(ndev, &ec);
}
}


static int fec_enet_get_tunable(struct net_device *netdev,
				const struct ethtool_tunable *tuna,
				void *data)
{
	struct fec_enet_private *fep = netdev_priv(netdev);
	int ret = 0;

	switch (tuna->id) {
	case ETHTOOL_RX_COPYBREAK:
		*(u32 *)data = fep->rx_copybreak;
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static int fec_enet_set_tunable(struct net_device *netdev,
				const struct ethtool_tunable *tuna,
				const void *data)
{
	struct fec_enet_private *fep = netdev_priv(netdev);
	int ret = 0;

	switch (tuna->id) {
	case ETHTOOL_RX_COPYBREAK:
		fep->rx_copybreak = *(u32 *)data;
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static const struct ethtool_ops fec_enet_ethtool_ops = {
static const struct ethtool_ops fec_enet_ethtool_ops = {
	.get_settings		= fec_enet_get_settings,
	.get_settings		= fec_enet_get_settings,
	.set_settings		= fec_enet_set_settings,
	.set_settings		= fec_enet_set_settings,
@@ -2408,6 +2496,8 @@ static const struct ethtool_ops fec_enet_ethtool_ops = {
	.get_sset_count		= fec_enet_get_sset_count,
	.get_sset_count		= fec_enet_get_sset_count,
#endif
#endif
	.get_ts_info		= fec_enet_get_ts_info,
	.get_ts_info		= fec_enet_get_ts_info,
	.get_tunable		= fec_enet_get_tunable,
	.set_tunable		= fec_enet_set_tunable,
};
};


static int fec_enet_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
static int fec_enet_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
@@ -2553,33 +2643,20 @@ fec_enet_alloc_rxq_buffers(struct net_device *ndev, unsigned int queue)
	struct sk_buff *skb;
	struct sk_buff *skb;
	struct bufdesc	*bdp;
	struct bufdesc	*bdp;
	struct fec_enet_priv_rx_q *rxq;
	struct fec_enet_priv_rx_q *rxq;
	unsigned int off;


	rxq = fep->rx_queue[queue];
	rxq = fep->rx_queue[queue];
	bdp = rxq->rx_bd_base;
	bdp = rxq->rx_bd_base;
	for (i = 0; i < rxq->rx_ring_size; i++) {
	for (i = 0; i < rxq->rx_ring_size; i++) {
		dma_addr_t addr;

		skb = netdev_alloc_skb(ndev, FEC_ENET_RX_FRSIZE);
		skb = netdev_alloc_skb(ndev, FEC_ENET_RX_FRSIZE);
		if (!skb)
		if (!skb)
			goto err_alloc;
			goto err_alloc;


		off = ((unsigned long)skb->data) & fep->rx_align;
		if (fec_enet_new_rxbdp(ndev, bdp, skb)) {
		if (off)
			skb_reserve(skb, fep->rx_align + 1 - off);

		addr = dma_map_single(&fep->pdev->dev, skb->data,
				FEC_ENET_RX_FRSIZE - fep->rx_align, DMA_FROM_DEVICE);

		if (dma_mapping_error(&fep->pdev->dev, addr)) {
			dev_kfree_skb(skb);
			dev_kfree_skb(skb);
			if (net_ratelimit())
				netdev_err(ndev, "Rx DMA memory map failed\n");
			goto err_alloc;
			goto err_alloc;
		}
		}


		rxq->rx_skbuff[i] = skb;
		rxq->rx_skbuff[i] = skb;
		bdp->cbd_bufaddr = addr;
		bdp->cbd_sc = BD_ENET_RX_EMPTY;
		bdp->cbd_sc = BD_ENET_RX_EMPTY;


		if (fep->bufdesc_ex) {
		if (fep->bufdesc_ex) {
@@ -3240,6 +3317,7 @@ fec_probe(struct platform_device *pdev)
	if (fep->bufdesc_ex && fep->ptp_clock)
	if (fep->bufdesc_ex && fep->ptp_clock)
		netdev_info(ndev, "registered PHC device %d\n", fep->dev_id);
		netdev_info(ndev, "registered PHC device %d\n", fep->dev_id);


	fep->rx_copybreak = COPYBREAK_DEFAULT;
	INIT_WORK(&fep->tx_timeout_work, fec_enet_timeout_work);
	INIT_WORK(&fep->tx_timeout_work, fec_enet_timeout_work);
	return 0;
	return 0;