smsc95xx: add tx checksum offload support
authorSteve Glendinning <steve.glendinning@smsc.com>
Thu, 20 Nov 2008 12:19:21 +0000 (04:19 -0800)
committerDavid S. Miller <davem@davemloft.net>
Thu, 20 Nov 2008 12:19:21 +0000 (04:19 -0800)
LAN9500 supports tx checksum offload, which slightly decreases cpu
utilisation.  The benefit isn't very large because we still require
the skb to be linearized, but it does save a few cycles.

This patch adds support for it, and enables it by default.

Signed-off-by: Steve Glendinning <steve.glendinning@smsc.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/smsc95xx.c

index e0d349f..4638a7b 100644 (file)
@@ -31,7 +31,7 @@
 #include "smsc95xx.h"
 
 #define SMSC_CHIPNAME                  "smsc95xx"
-#define SMSC_DRIVER_VERSION            "1.0.3"
+#define SMSC_DRIVER_VERSION            "1.0.4"
 #define HS_USB_PKT_SIZE                        (512)
 #define FS_USB_PKT_SIZE                        (64)
 #define DEFAULT_HS_BURST_CAP_SIZE      (16 * 1024 + 5 * HS_USB_PKT_SIZE)
 #define MAX_SINGLE_PACKET_SIZE         (2048)
 #define LAN95XX_EEPROM_MAGIC           (0x9500)
 #define EEPROM_MAC_OFFSET              (0x01)
+#define DEFAULT_TX_CSUM_ENABLE         (true)
 #define DEFAULT_RX_CSUM_ENABLE         (true)
 #define SMSC95XX_INTERNAL_PHY_ID       (1)
 #define SMSC95XX_TX_OVERHEAD           (8)
+#define SMSC95XX_TX_OVERHEAD_CSUM      (12)
 #define FLOW_CTRL_TX                   (1)
 #define FLOW_CTRL_RX                   (2)
 
 struct smsc95xx_priv {
        u32 mac_cr;
        spinlock_t mac_cr_lock;
+       bool use_tx_csum;
        bool use_rx_csum;
 };
 
@@ -556,9 +559,10 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb)
                devwarn(dev, "unexpected interrupt, intdata=0x%08X", intdata);
 }
 
-/* Enable or disable Rx checksum offload engine */
-static int smsc95xx_set_rx_csum(struct usbnet *dev, bool enable)
+/* Enable or disable Tx & Rx checksum offload engines */
+static int smsc95xx_set_csums(struct usbnet *dev)
 {
+       struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
        u32 read_buf;
        int ret = smsc95xx_read_reg(dev, COE_CR, &read_buf);
        if (ret < 0) {
@@ -566,7 +570,12 @@ static int smsc95xx_set_rx_csum(struct usbnet *dev, bool enable)
                return ret;
        }
 
-       if (enable)
+       if (pdata->use_tx_csum)
+               read_buf |= Tx_COE_EN_;
+       else
+               read_buf &= ~Tx_COE_EN_;
+
+       if (pdata->use_rx_csum)
                read_buf |= Rx_COE_EN_;
        else
                read_buf &= ~Rx_COE_EN_;
@@ -626,7 +635,26 @@ static int smsc95xx_ethtool_set_rx_csum(struct net_device *netdev, u32 val)
 
        pdata->use_rx_csum = !!val;
 
-       return smsc95xx_set_rx_csum(dev, pdata->use_rx_csum);
+       return smsc95xx_set_csums(dev);
+}
+
+static u32 smsc95xx_ethtool_get_tx_csum(struct net_device *netdev)
+{
+       struct usbnet *dev = netdev_priv(netdev);
+       struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+
+       return pdata->use_tx_csum;
+}
+
+static int smsc95xx_ethtool_set_tx_csum(struct net_device *netdev, u32 val)
+{
+       struct usbnet *dev = netdev_priv(netdev);
+       struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+
+       pdata->use_tx_csum = !!val;
+
+       ethtool_op_set_tx_hw_csum(netdev, pdata->use_tx_csum);
+       return smsc95xx_set_csums(dev);
 }
 
 static struct ethtool_ops smsc95xx_ethtool_ops = {
@@ -640,6 +668,8 @@ static struct ethtool_ops smsc95xx_ethtool_ops = {
        .get_eeprom_len = smsc95xx_ethtool_get_eeprom_len,
        .get_eeprom     = smsc95xx_ethtool_get_eeprom,
        .set_eeprom     = smsc95xx_ethtool_set_eeprom,
+       .get_tx_csum    = smsc95xx_ethtool_get_tx_csum,
+       .set_tx_csum    = smsc95xx_ethtool_set_tx_csum,
        .get_rx_csum    = smsc95xx_ethtool_get_rx_csum,
        .set_rx_csum    = smsc95xx_ethtool_set_rx_csum,
 };
@@ -757,6 +787,7 @@ static int smsc95xx_phy_initialize(struct usbnet *dev)
 static int smsc95xx_reset(struct usbnet *dev)
 {
        struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+       struct net_device *netdev = dev->net;
        u32 read_buf, write_buf, burst_cap;
        int ret = 0, timeout;
 
@@ -968,10 +999,11 @@ static int smsc95xx_reset(struct usbnet *dev)
                return ret;
        }
 
-       /* Enable or disable Rx checksum offload engine */
-       ret = smsc95xx_set_rx_csum(dev, pdata->use_rx_csum);
+       /* Enable or disable checksum offload engines */
+       ethtool_op_set_tx_hw_csum(netdev, pdata->use_tx_csum);
+       ret = smsc95xx_set_csums(dev);
        if (ret < 0) {
-               devwarn(dev, "Failed to set Rx csum offload: %d", ret);
+               devwarn(dev, "Failed to set csum offload: %d", ret);
                return ret;
        }
 
@@ -1027,6 +1059,7 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
 
        spin_lock_init(&pdata->mac_cr_lock);
 
+       pdata->use_tx_csum = DEFAULT_TX_CSUM_ENABLE;
        pdata->use_rx_csum = DEFAULT_RX_CSUM_ENABLE;
 
        /* Init all registers */
@@ -1146,22 +1179,44 @@ static int smsc95xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
        return 1;
 }
 
+static u32 smsc95xx_calc_csum_preamble(struct sk_buff *skb)
+{
+       int len = skb->data - skb->head;
+       u16 high_16 = (u16)(skb->csum_offset + skb->csum_start - len);
+       u16 low_16 = (u16)(skb->csum_start - len);
+       return (high_16 << 16) | low_16;
+}
+
 static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev,
                                         struct sk_buff *skb, gfp_t flags)
 {
+       struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+       bool csum = pdata->use_tx_csum && (skb->ip_summed == CHECKSUM_PARTIAL);
+       int overhead = csum ? SMSC95XX_TX_OVERHEAD_CSUM : SMSC95XX_TX_OVERHEAD;
        u32 tx_cmd_a, tx_cmd_b;
 
-       if (skb_headroom(skb) < SMSC95XX_TX_OVERHEAD) {
+       /* We do not advertise SG, so skbs should be already linearized */
+       BUG_ON(skb_shinfo(skb)->nr_frags);
+
+       if (skb_headroom(skb) < overhead) {
                struct sk_buff *skb2 = skb_copy_expand(skb,
-                       SMSC95XX_TX_OVERHEAD, 0, flags);
+                       overhead, 0, flags);
                dev_kfree_skb_any(skb);
                skb = skb2;
                if (!skb)
                        return NULL;
        }
 
+       if (csum) {
+               u32 csum_preamble = smsc95xx_calc_csum_preamble(skb);
+               skb_push(skb, 4);
+               memcpy(skb->data, &csum_preamble, 4);
+       }
+
        skb_push(skb, 4);
        tx_cmd_b = (u32)(skb->len - 4);
+       if (csum)
+               tx_cmd_b |= TX_CMD_B_CSUM_ENABLE;
        cpu_to_le32s(&tx_cmd_b);
        memcpy(skb->data, &tx_cmd_b, 4);