This patch enables TSO(v4) hw feature for emac driver.
As atleast the APM82181's TCP/IP acceleration hardware
controller (TAH) provides TCP segmentation support in
the transmit path.

Signed-off-by: Christian Lamparter <chunk...@gmail.com>
---
 drivers/net/ethernet/ibm/emac/core.c | 101 ++++++++++++++++++++++++++-
 drivers/net/ethernet/ibm/emac/core.h |   4 ++
 drivers/net/ethernet/ibm/emac/emac.h |   7 ++
 drivers/net/ethernet/ibm/emac/tah.c  |  20 ++++++
 drivers/net/ethernet/ibm/emac/tah.h  |   2 +
 5 files changed, 133 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/ibm/emac/core.c 
b/drivers/net/ethernet/ibm/emac/core.c
index be560f9031f4..49ffbd6e1707 100644
--- a/drivers/net/ethernet/ibm/emac/core.c
+++ b/drivers/net/ethernet/ibm/emac/core.c
@@ -38,6 +38,9 @@
 #include <linux/mii.h>
 #include <linux/bitops.h>
 #include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
 #include <linux/workqueue.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
@@ -1410,6 +1413,52 @@ static inline u16 emac_tx_csum(struct emac_instance *dev,
        return 0;
 }
 
+const u32 tah_ss[TAH_NO_SSR] = { 9000, 4500, 1500, 1300, 576, 176 };
+
+static int emac_tx_tso(struct emac_instance *dev, struct sk_buff *skb,
+                      u16 *ctrl)
+{
+       if (emac_has_feature(dev, EMAC_FTR_TAH_HAS_TSO) &&
+           skb_is_gso(skb) && !!(skb_shinfo(skb)->gso_type &
+                               (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))) {
+               u32 seg_size = 0, i;
+
+               /* Get the MTU */
+               seg_size = skb_shinfo(skb)->gso_size + tcp_hdrlen(skb)
+                       + skb_network_header_len(skb);
+
+               /* Restriction applied for the segmentation size
+                * to use HW segmentation offload feature: the size
+                * of the segment must not be less than 168 bytes for
+                * DIX formatted segments, or 176 bytes for
+                * IEEE formatted segments.
+                *
+                * I use value 176 to check for the segment size here
+                * as it can cover both 2 conditions above.
+                */
+               if (seg_size < 176)
+                       return -ENODEV;
+
+               /* Get the best suitable MTU */
+               for (i = 0; i < ARRAY_SIZE(tah_ss); i++) {
+                       u32 curr_seg = tah_ss[i];
+
+                       if (curr_seg > dev->ndev->mtu ||
+                           curr_seg > seg_size)
+                               continue;
+
+                       *ctrl &= ~EMAC_TX_CTRL_TAH_CSUM;
+                       *ctrl |= EMAC_TX_CTRL_TAH_SSR(i);
+                       return 0;
+               }
+
+               /* none found fall back to software */
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static inline netdev_tx_t emac_xmit_finish(struct emac_instance *dev, int len)
 {
        struct emac_regs __iomem *p = dev->emacp;
@@ -1452,8 +1501,46 @@ static inline u16 emac_tx_vlan(struct emac_instance 
*dev, struct sk_buff *skb)
        return 0;
 }
 
+static netdev_tx_t
+emac_start_xmit(struct sk_buff *skb, struct net_device *ndev);
+
+static netdev_tx_t emac_sw_tso(struct sk_buff *skb, struct net_device *ndev)
+{
+       struct emac_instance *dev = netdev_priv(ndev);
+       struct sk_buff *segs, *curr;
+
+       segs = skb_gso_segment(skb, ndev->features &
+                                       ~(NETIF_F_TSO | NETIF_F_TSO6));
+       if (IS_ERR_OR_NULL(segs)) {
+               goto drop;
+       } else {
+               while (segs) {
+                       /* check for overflow */
+                       if (dev->tx_cnt >= NUM_TX_BUFF) {
+                               dev_kfree_skb_any(segs);
+                               goto drop;
+                       }
+
+                       curr = segs;
+                       segs = curr->next;
+                       curr->next = NULL;
+
+                       emac_start_xmit(curr, ndev);
+               }
+               dev_consume_skb_any(skb);
+       }
+
+       return NETDEV_TX_OK;
+
+drop:
+       ++dev->estats.tx_dropped;
+       dev_kfree_skb_any(skb);
+       return NETDEV_TX_OK;
+}
+
 /* Tx lock BH */
-static netdev_tx_t emac_start_xmit(struct sk_buff *skb, struct net_device 
*ndev)
+static netdev_tx_t
+emac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
 {
        struct emac_instance *dev = netdev_priv(ndev);
        unsigned int len = skb->len;
@@ -1462,6 +1549,9 @@ static netdev_tx_t emac_start_xmit(struct sk_buff *skb, 
struct net_device *ndev)
        u16 ctrl = EMAC_TX_CTRL_GFCS | EMAC_TX_CTRL_GP | MAL_TX_CTRL_READY |
            MAL_TX_CTRL_LAST | emac_tx_csum(dev, skb) | emac_tx_vlan(dev, skb);
 
+       if (emac_tx_tso(dev, skb, &ctrl))
+               return emac_sw_tso(skb, ndev);
+
        slot = dev->tx_slot++;
        if (dev->tx_slot == NUM_TX_BUFF) {
                dev->tx_slot = 0;
@@ -1536,6 +1626,9 @@ emac_start_xmit_sg(struct sk_buff *skb, struct net_device 
*ndev)
 
        ctrl = EMAC_TX_CTRL_GFCS | EMAC_TX_CTRL_GP | MAL_TX_CTRL_READY |
            emac_tx_csum(dev, skb) | emac_tx_vlan(dev, skb);
+       if (emac_tx_tso(dev, skb, &ctrl))
+               return emac_sw_tso(skb, ndev);
+
        slot = dev->tx_slot;
 
        /* skb data */
@@ -2946,6 +3039,9 @@ static int emac_init_config(struct emac_instance *dev)
        if (dev->tah_ph != 0) {
 #ifdef CONFIG_IBM_EMAC_TAH
                dev->features |= EMAC_FTR_HAS_TAH;
+
+               if (of_device_is_compatible(np, "ibm,emac-apm821xx"))
+                       dev->features |= EMAC_FTR_TAH_HAS_TSO;
 #else
                printk(KERN_ERR "%pOF: TAH support not enabled !\n", np);
                return -ENXIO;
@@ -3167,6 +3263,9 @@ static int emac_probe(struct platform_device *ofdev)
        if (dev->tah_dev) {
                ndev->hw_features = NETIF_F_IP_CSUM | NETIF_F_SG;
 
+               if (emac_has_feature(dev, EMAC_FTR_TAH_HAS_TSO))
+                       ndev->hw_features |= NETIF_F_TSO;
+
                if (emac_has_feature(dev, EMAC_FTR_HAS_VLAN_CTAG_TX)) {
                        ndev->vlan_features |= ndev->hw_features;
                        ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_TX;
diff --git a/drivers/net/ethernet/ibm/emac/core.h 
b/drivers/net/ethernet/ibm/emac/core.h
index 8d84d439168c..e40ccee58775 100644
--- a/drivers/net/ethernet/ibm/emac/core.h
+++ b/drivers/net/ethernet/ibm/emac/core.h
@@ -336,6 +336,8 @@ struct emac_instance {
 #define EMAC_FTR_APM821XX_NO_HALF_DUPLEX       0x00001000
 /* EMAC can insert 802.1Q tag */
 #define EMAC_FTR_HAS_VLAN_CTAG_TX              0x00002000
+/* TAH can do TCP segmentation offload */
+#define EMAC_FTR_TAH_HAS_TSO                   0x00004000
 
 /* Right now, we don't quite handle the always/possible masks on the
  * most optimal way as we don't have a way to say something like
@@ -352,6 +354,8 @@ enum {
 #endif
 #ifdef CONFIG_IBM_EMAC_TAH
            EMAC_FTR_HAS_TAH    |
+           EMAC_FTR_TAH_HAS_TSO        |
+
 #endif
 #ifdef CONFIG_IBM_EMAC_ZMII
            EMAC_FTR_HAS_ZMII   |
diff --git a/drivers/net/ethernet/ibm/emac/emac.h 
b/drivers/net/ethernet/ibm/emac/emac.h
index e2f80cca9bed..833967aceb2f 100644
--- a/drivers/net/ethernet/ibm/emac/emac.h
+++ b/drivers/net/ethernet/ibm/emac/emac.h
@@ -266,6 +266,13 @@ struct emac_regs {
 #define EMAC_TX_CTRL_IVT               0x0020
 #define EMAC_TX_CTRL_RVT               0x0010
 #define EMAC_TX_CTRL_TAH_CSUM          0x000e
+#define EMAC_TX_CTRL_TAH_SSR(idx)      (((idx) + 1) << 1)
+#define EMAC_TX_CTRL_TAH_SSR5          0x000c
+#define EMAC_TX_CTRL_TAH_SSR4          0x000a
+#define EMAC_TX_CTRL_TAH_SSR3          0x0008
+#define EMAC_TX_CTRL_TAH_SSR2          0x0006
+#define EMAC_TX_CTRL_TAH_SSR1          0x0004
+#define EMAC_TX_CTRL_TAH_SSR0          0x0002
 
 /* EMAC specific TX descriptor status fields (read access) */
 #define EMAC_TX_ST_BFCS                        0x0200
diff --git a/drivers/net/ethernet/ibm/emac/tah.c 
b/drivers/net/ethernet/ibm/emac/tah.c
index 9912456dca48..a0236eb305ae 100644
--- a/drivers/net/ethernet/ibm/emac/tah.c
+++ b/drivers/net/ethernet/ibm/emac/tah.c
@@ -45,6 +45,24 @@ void tah_detach(struct platform_device *ofdev, int channel)
        mutex_unlock(&dev->lock);
 }
 
+static void tah_set_ssr(struct platform_device *ofdev)
+{
+       struct tah_instance *dev = dev_get_drvdata(&ofdev->dev);
+       struct tah_regs __iomem *p = dev->base;
+       int i;
+
+       mutex_lock(&dev->lock);
+
+       for (i = 0; i < ARRAY_SIZE(tah_ss); i++) {
+               /* Segment size can be up to 16K, but needs
+                * to be a multiple of 2 bytes
+                */
+               out_be32(&p->ssr0 + i, (tah_ss[i] & 0x3ffe) << 16);
+       }
+
+       mutex_unlock(&dev->lock);
+}
+
 void tah_reset(struct platform_device *ofdev)
 {
        struct tah_instance *dev = platform_get_drvdata(ofdev);
@@ -64,6 +82,8 @@ void tah_reset(struct platform_device *ofdev)
        out_be32(&p->mr,
                 TAH_MR_CVR | TAH_MR_ST_768 | TAH_MR_TFS_10KB | TAH_MR_DTFP |
                 TAH_MR_DIG);
+
+       tah_set_ssr(ofdev);
 }
 
 int tah_get_regs_len(struct platform_device *ofdev)
diff --git a/drivers/net/ethernet/ibm/emac/tah.h 
b/drivers/net/ethernet/ibm/emac/tah.h
index 4d5f336f07b3..2cb0629f30e2 100644
--- a/drivers/net/ethernet/ibm/emac/tah.h
+++ b/drivers/net/ethernet/ibm/emac/tah.h
@@ -36,6 +36,8 @@ struct tah_regs {
        u32 tsr;
 };
 
+#define TAH_NO_SSR     6
+extern const u32 tah_ss[TAH_NO_SSR];
 
 /* TAH device */
 struct tah_instance {
-- 
2.19.1

Reply via email to