summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/ethernet/freescale/fec_main.c195
1 files changed, 149 insertions, 46 deletions
diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c
index 496e60228521..45ceac59ae61 100644
--- a/drivers/net/ethernet/freescale/fec_main.c
+++ b/drivers/net/ethernet/freescale/fec_main.c
@@ -109,6 +109,8 @@ static void set_multicast_list(struct net_device *ndev);
#define FEC_QUIRK_HAS_CSUM (1 << 5)
/* Controller has hardware vlan support */
#define FEC_QUIRK_HAS_VLAN (1 << 6)
+/* Controller has ability to offset rx packets */
+#define FEC_QUIRK_RX_SHIFT16 (1 << 8)
static struct platform_device_id fec_devtype[] = {
{
@@ -128,7 +130,7 @@ static struct platform_device_id fec_devtype[] = {
.name = "imx6q-fec",
.driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT |
FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM |
- FEC_QUIRK_HAS_VLAN,
+ FEC_QUIRK_HAS_VLAN | FEC_QUIRK_RX_SHIFT16,
}, {
.name = "mvf600-fec",
.driver_data = FEC_QUIRK_ENET_MAC,
@@ -207,6 +209,7 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
/* FEC receive acceleration */
#define FEC_RACC_IPDIS (1 << 1)
#define FEC_RACC_PRODIS (1 << 2)
+#define FEC_RACC_SHIFT16 BIT(7)
#define FEC_RACC_OPTIONS (FEC_RACC_IPDIS | FEC_RACC_PRODIS)
/*
@@ -247,6 +250,20 @@ MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
static int mii_cnt;
+static unsigned copybreak = 200;
+module_param(copybreak, uint, 0644);
+MODULE_PARM_DESC(copybreak,
+ "Maximum size of packet that is copied to a new buffer on receive");
+
+static bool fec_enet_rx_zerocopy(struct fec_enet_private *fep, unsigned pktlen)
+{
+#ifndef CONFIG_M5272
+ if (fep->quirks & FEC_QUIRK_RX_SHIFT16 && pktlen >= copybreak)
+ return true;
+#endif
+ return false;
+}
+
static union bufdesc_u *
fec_enet_tx_get(unsigned int index, struct fec_enet_private *fep)
{
@@ -669,6 +686,8 @@ fec_restart(struct net_device *ndev)
#if !defined(CONFIG_M5272)
/* set RX checksum */
val = readl(fep->hwp + FEC_RACC);
+ if (fep->quirks & FEC_QUIRK_RX_SHIFT16)
+ val |= FEC_RACC_SHIFT16;
if (fep->flags & FEC_FLAG_RX_CSUM)
val |= FEC_RACC_OPTIONS;
else
@@ -987,6 +1006,68 @@ fec_enet_receive(struct sk_buff *skb, union bufdesc_u *bdp, struct net_device *n
napi_gro_receive(&fep->napi, skb);
}
+static void
+fec_enet_receive_nocopy(unsigned pkt_len, unsigned index, union bufdesc_u *bdp,
+ struct net_device *ndev)
+{
+ struct fec_enet_private *fep = netdev_priv(ndev);
+ struct sk_buff *skb, *skb_new;
+ unsigned char *data;
+ dma_addr_t addr;
+
+ skb_new = netdev_alloc_skb(ndev, FEC_ENET_RX_FRSIZE);
+ if (!skb_new) {
+ ndev->stats.rx_dropped++;
+ return;
+ }
+
+ addr = dma_map_single(&fep->pdev->dev, skb_new->data,
+ FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&fep->pdev->dev, addr)) {
+ dev_kfree_skb(skb_new);
+ ndev->stats.rx_dropped++;
+ return;
+ }
+
+ /* We have the new skb, so proceed to deal with the received data. */
+ dma_unmap_single(&fep->pdev->dev, bdp->bd.cbd_bufaddr,
+ FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE);
+
+ skb = fep->rx_skbuff[index];
+
+ /* Now subsitute in the new skb */
+ fep->rx_skbuff[index] = skb_new;
+ bdp->bd.cbd_bufaddr = addr;
+
+ /*
+ * Update the skb length according to the raw packet length.
+ * Then remove the two bytes of additional padding.
+ */
+ skb_put(skb, pkt_len);
+ data = skb_pull_inline(skb, 2);
+
+ if (fep->quirks & FEC_QUIRK_SWAP_FRAME)
+ swap_buffer(data, skb->len);
+
+ /*
+ * Now juggle things for the VLAN tag - if the hardware
+ * flags this as present, we need to read the tag, and
+ * then shuffle the ethernet addresses up.
+ */
+ if (ndev->features & NETIF_F_HW_VLAN_CTAG_RX &&
+ bdp->ebd.cbd_esc & BD_ENET_RX_VLAN) {
+ struct vlan_hdr *vlan = (struct vlan_hdr *)(data + ETH_HLEN);
+
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
+ ntohs(vlan->h_vlan_TCI));
+
+ memmove(data + VLAN_HLEN, data, 2 * ETH_ALEN);
+ skb_pull_inline(skb, VLAN_HLEN);
+ }
+
+ fec_enet_receive(skb, bdp, ndev);
+}
+
/* During a receive, the rx_next points to the current incoming buffer.
* When we update through the ring, if the next incoming buffer has
* not been given to the system, we just set the empty indicator,
@@ -996,7 +1077,6 @@ static int
fec_enet_rx(struct net_device *ndev, int budget)
{
struct fec_enet_private *fep = netdev_priv(ndev);
- struct sk_buff *skb;
ushort pkt_len;
__u8 *data;
int pkt_received = 0;
@@ -1012,6 +1092,7 @@ fec_enet_rx(struct net_device *ndev, int budget)
do {
union bufdesc_u *bdp = fec_enet_rx_get(index, fep);
unsigned status, cbd_esc;
+ struct sk_buff *skb;
status = bdp->bd.cbd_sc;
if (status & BD_ENET_RX_EMPTY)
@@ -1074,62 +1155,84 @@ fec_enet_rx(struct net_device *ndev, int budget)
pkt_len = bdp->bd.cbd_datlen - 4;
ndev->stats.rx_bytes += pkt_len;
- if (fep->flags & FEC_FLAG_BUFDESC_EX) {
- cbd_esc = bdp->ebd.cbd_esc;
- if (!(fep->flags & FEC_FLAG_RX_VLAN))
- cbd_esc &= ~BD_ENET_RX_VLAN;
+ if (fec_enet_rx_zerocopy(fep, pkt_len)) {
+ fec_enet_receive_nocopy(pkt_len, index, bdp, ndev);
} else {
- cbd_esc = 0;
- }
+ if (fep->flags & FEC_FLAG_BUFDESC_EX) {
+ cbd_esc = bdp->ebd.cbd_esc;
+ if (!(fep->flags & FEC_FLAG_RX_VLAN))
+ cbd_esc &= ~BD_ENET_RX_VLAN;
+ } else {
+ cbd_esc = 0;
+ }
- /*
- * Detect the presence of the VLANG tag, and just
- * the packet length appropriately.
- */
- if (cbd_esc & BD_ENET_RX_VLAN)
- pkt_len -= VLAN_HLEN;
+ /*
+ * Detect the presence of the VLANG tag, and just
+ * the packet length appropriately.
+ */
+ if (cbd_esc & BD_ENET_RX_VLAN)
+ pkt_len -= VLAN_HLEN;
- /* This does 16 byte alignment, exactly what we need. */
- skb = netdev_alloc_skb(ndev, pkt_len + NET_IP_ALIGN);
- if (unlikely(!skb)) {
- ndev->stats.rx_dropped++;
- goto rx_processing_done;
- }
+ /*
+ * This does 16 byte alignment, exactly what we need.
+ */
+ skb = netdev_alloc_skb(ndev, pkt_len + NET_IP_ALIGN);
+ if (unlikely(!skb)) {
+ ndev->stats.rx_dropped++;
+ goto rx_processing_done;
+ }
- data = fep->rx_skbuff[index]->data;
- dma_sync_single_for_cpu(&fep->pdev->dev, bdp->bd.cbd_bufaddr,
- FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE);
+ dma_sync_single_for_cpu(&fep->pdev->dev,
+ bdp->bd.cbd_bufaddr,
+ FEC_ENET_RX_FRSIZE,
+ DMA_FROM_DEVICE);
- if (fep->quirks & FEC_QUIRK_SWAP_FRAME)
- swap_buffer(data, pkt_len);
+ data = fep->rx_skbuff[index]->data;
- skb_reserve(skb, NET_IP_ALIGN);
- skb_put(skb, pkt_len); /* Make room */
+#ifndef CONFIG_M5272
+ /*
+ * If we have enabled this feature, we need to discard
+ * the two bytes at the beginning of the packet before
+ * copying it.
+ */
+ if (fep->quirks & FEC_QUIRK_RX_SHIFT16) {
+ pkt_len -= 2;
+ data += 2;
+ }
+#endif
- /* If this is a VLAN packet remove the VLAN Tag */
- if (cbd_esc & BD_ENET_RX_VLAN) {
- /* Push and remove the vlan tag */
- struct vlan_hdr *vlan =
+ if (fep->quirks & FEC_QUIRK_SWAP_FRAME)
+ swap_buffer(data, pkt_len);
+
+ skb_reserve(skb, NET_IP_ALIGN);
+ skb_put(skb, pkt_len); /* Make room */
+
+ /* If this is a VLAN packet remove the VLAN Tag */
+ if (cbd_esc & BD_ENET_RX_VLAN) {
+ /* Push and remove the vlan tag */
+ struct vlan_hdr *vlan =
(struct vlan_hdr *) (data + ETH_HLEN);
- __vlan_hwaccel_put_tag(skb,
- htons(ETH_P_8021Q),
- ntohs(vlan->h_vlan_TCI));
+ __vlan_hwaccel_put_tag(skb,
+ htons(ETH_P_8021Q),
+ ntohs(vlan->h_vlan_TCI));
- /* Extract the frame data without the VLAN header. */
- skb_copy_to_linear_data(skb, data, 2 * ETH_ALEN);
- skb_copy_to_linear_data_offset(skb, 2 * ETH_ALEN,
- data + 2 * ETH_ALEN +
- VLAN_HLEN,
- pkt_len - 2 * ETH_ALEN);
- } else {
- skb_copy_to_linear_data(skb, data, pkt_len);
- }
+ /* Extract the frame data without the VLAN header. */
+ skb_copy_to_linear_data(skb, data, 2 * ETH_ALEN);
+ skb_copy_to_linear_data_offset(skb, 2 * ETH_ALEN,
+ data + 2 * ETH_ALEN + VLAN_HLEN,
+ pkt_len - 2 * ETH_ALEN);
+ } else {
+ skb_copy_to_linear_data(skb, data, pkt_len);
+ }
- dma_sync_single_for_device(&fep->pdev->dev, bdp->bd.cbd_bufaddr,
- FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE);
+ dma_sync_single_for_device(&fep->pdev->dev,
+ bdp->bd.cbd_bufaddr,
+ FEC_ENET_RX_FRSIZE,
+ DMA_FROM_DEVICE);
- fec_enet_receive(skb, bdp, ndev);
+ fec_enet_receive(skb, bdp, ndev);
+ }
rx_processing_done:
if (fep->flags & FEC_FLAG_BUFDESC_EX) {