Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[platform/kernel/linux-starfive.git] / drivers / net / usb / smsc95xx.c
index f2d01cb..e3d0862 100644 (file)
@@ -618,9 +618,7 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb)
                return;
        }
 
-       memcpy(&intdata, urb->transfer_buffer, 4);
-       le32_to_cpus(&intdata);
-
+       intdata = get_unaligned_le32(urb->transfer_buffer);
        netif_dbg(dev, link, dev->net, "intdata: 0x%08X\n", intdata);
 
        if (intdata & INT_ENP_PHY_INT_)
@@ -1295,6 +1293,7 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
                dev->net->features |= NETIF_F_RXCSUM;
 
        dev->net->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM;
+       set_bit(EVENT_NO_IP_ALIGN, &dev->flags);
 
        smsc95xx_init_mac_address(dev);
 
@@ -1933,8 +1932,7 @@ static int smsc95xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
                unsigned char *packet;
                u16 size;
 
-               memcpy(&header, skb->data, sizeof(header));
-               le32_to_cpus(&header);
+               header = get_unaligned_le32(skb->data);
                skb_pull(skb, 4 + NET_IP_ALIGN);
                packet = skb->data;
 
@@ -2011,12 +2009,30 @@ static u32 smsc95xx_calc_csum_preamble(struct sk_buff *skb)
        return (high_16 << 16) | low_16;
 }
 
+/* The TX CSUM won't work if the checksum lies in the last 4 bytes of the
+ * transmission. This is fairly unlikely, only seems to trigger with some
+ * short TCP ACK packets sent.
+ *
+ * Note, this calculation should probably check for the alignment of the
+ * data as well, but a straight check for csum being in the last four bytes
+ * of the packet should be ok for now.
+ */
+static bool smsc95xx_can_tx_checksum(struct sk_buff *skb)
+{
+       unsigned int len = skb->len - skb_checksum_start_offset(skb);
+
+       if (skb->len <= 45)
+              return false;
+       return skb->csum_offset < (len - (4 + 1));
+}
+
 static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev,
                                         struct sk_buff *skb, gfp_t flags)
 {
        bool csum = skb->ip_summed == CHECKSUM_PARTIAL;
        int overhead = csum ? SMSC95XX_TX_OVERHEAD_CSUM : SMSC95XX_TX_OVERHEAD;
        u32 tx_cmd_a, tx_cmd_b;
+       void *ptr;
 
        /* We do not advertise SG, so skbs should be already linearized */
        BUG_ON(skb_shinfo(skb)->nr_frags);
@@ -2030,8 +2046,11 @@ static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev,
                return NULL;
        }
 
+       tx_cmd_b = (u32)skb->len;
+       tx_cmd_a = tx_cmd_b | TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_;
+
        if (csum) {
-               if (skb->len <= 45) {
+               if (!smsc95xx_can_tx_checksum(skb)) {
                        /* workaround - hardware tx checksum does not work
                         * properly with extremely small packets */
                        long csstart = skb_checksum_start_offset(skb);
@@ -2043,24 +2062,18 @@ static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev,
                        csum = false;
                } else {
                        u32 csum_preamble = smsc95xx_calc_csum_preamble(skb);
-                       skb_push(skb, 4);
-                       cpu_to_le32s(&csum_preamble);
-                       memcpy(skb->data, &csum_preamble, 4);
+                       ptr = skb_push(skb, 4);
+                       put_unaligned_le32(csum_preamble, ptr);
+
+                       tx_cmd_a += 4;
+                       tx_cmd_b += 4;
+                       tx_cmd_b |= TX_CMD_B_CSUM_ENABLE;
                }
        }
 
-       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);
-
-       skb_push(skb, 4);
-       tx_cmd_a = (u32)(skb->len - 8) | TX_CMD_A_FIRST_SEG_ |
-               TX_CMD_A_LAST_SEG_;
-       cpu_to_le32s(&tx_cmd_a);
-       memcpy(skb->data, &tx_cmd_a, 4);
+       ptr = skb_push(skb, 8);
+       put_unaligned_le32(tx_cmd_a, ptr);
+       put_unaligned_le32(tx_cmd_b, ptr+4);
 
        return skb;
 }