Merge branch 'stable-3.2' into pandora-3.2
[pandora-kernel.git] / drivers / net / usb / asix.c
index f3fedde..27d1c45 100644 (file)
@@ -183,6 +183,17 @@ struct ax88172_int_data {
        __le16 res3;
 } __packed;
 
+struct asix_rx_fixup_info {
+       struct sk_buff *ax_skb;
+       u32 header;
+       u16 size;
+       bool split_head;
+};
+
+struct asix_common_private {
+       struct asix_rx_fixup_info rx_fixup_info;
+};
+
 static int asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
                            u16 size, void *data)
 {
@@ -304,97 +315,89 @@ asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index,
        }
 }
 
-static int asix_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+static int asix_rx_fixup_internal(struct usbnet *dev, struct sk_buff *skb,
+                                 struct asix_rx_fixup_info *rx)
 {
-       u8  *head;
-       u32  header;
-       char *packet;
-       struct sk_buff *ax_skb;
-       u16 size;
+       int offset = 0;
+
+       while (offset + sizeof(u16) <= skb->len) {
+               u16 remaining = 0;
+               unsigned char *data;
+
+               if (!rx->size) {
+                       if ((skb->len - offset == sizeof(u16)) ||
+                           rx->split_head) {
+                               if(!rx->split_head) {
+                                       rx->header = get_unaligned_le16(
+                                                       skb->data + offset);
+                                       rx->split_head = true;
+                                       offset += sizeof(u16);
+                                       break;
+                               } else {
+                                       rx->header |= (get_unaligned_le16(
+                                                       skb->data + offset)
+                                                       << 16);
+                                       rx->split_head = false;
+                                       offset += sizeof(u16);
+                               }
+                       } else {
+                               rx->header = get_unaligned_le32(skb->data +
+                                                               offset);
+                               offset += sizeof(u32);
+                       }
 
-       head = (u8 *) skb->data;
-       memcpy(&header, head, sizeof(header));
-       le32_to_cpus(&header);
-       packet = head + sizeof(header);
-
-       skb_pull(skb, 4);
-
-       while (skb->len > 0) {
-               if ((header & 0x07ff) != ((~header >> 16) & 0x07ff))
-                       netdev_err(dev->net, "asix_rx_fixup() Bad Header Length\n");
-
-               /* get the packet length */
-               size = (u16) (header & 0x000007ff);
-
-               if ((skb->len) - ((size + 1) & 0xfffe) == 0) {
-                       u8 alignment = (unsigned long)skb->data & 0x3;
-                       if (alignment != 0x2) {
-                               /*
-                                * not 16bit aligned so use the room provided by
-                                * the 32 bit header to align the data
-                                *
-                                * note we want 16bit alignment as MAC header is
-                                * 14bytes thus ip header will be aligned on
-                                * 32bit boundary so accessing ipheader elements
-                                * using a cast to struct ip header wont cause
-                                * an unaligned accesses.
-                                */
-                               u8 realignment = (alignment + 2) & 0x3;
-                               memmove(skb->data - realignment,
-                                       skb->data,
-                                       size);
-                               skb->data -= realignment;
-                               skb_set_tail_pointer(skb, size);
+                       /* get the packet length */
+                       rx->size = (u16) (rx->header & 0x7ff);
+                       if (rx->size != ((~rx->header >> 16) & 0x7ff)) {
+                               netdev_err(dev->net, "asix_rx_fixup() Bad Header Length 0x%x, offset %d\n",
+                                          rx->header, offset);
+                               rx->size = 0;
+                               return 0;
                        }
-                       return 2;
+                       rx->ax_skb = netdev_alloc_skb_ip_align(dev->net,
+                                                              rx->size);
+                       if (!rx->ax_skb)
+                               return 0;
                }
 
-               if (size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) {
+               if (rx->size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) {
                        netdev_err(dev->net, "asix_rx_fixup() Bad RX Length %d\n",
-                                  size);
-                       return 0;
-               }
-               ax_skb = skb_clone(skb, GFP_ATOMIC);
-               if (ax_skb) {
-                       u8 alignment = (unsigned long)packet & 0x3;
-                       ax_skb->len = size;
-
-                       if (alignment != 0x2) {
-                               /*
-                                * not 16bit aligned use the room provided by
-                                * the 32 bit header to align the data
-                                */
-                               u8 realignment = (alignment + 2) & 0x3;
-                               memmove(packet - realignment, packet, size);
-                               packet -= realignment;
-                       }
-                       ax_skb->data = packet;
-                       skb_set_tail_pointer(ax_skb, size);
-                       usbnet_skb_return(dev, ax_skb);
-               } else {
+                                  rx->size);
+                       kfree_skb(rx->ax_skb);
                        return 0;
                }
 
-               skb_pull(skb, (size + 1) & 0xfffe);
+               if (rx->size > skb->len - offset) {
+                       remaining = rx->size - (skb->len - offset);
+                       rx->size = skb->len - offset;
+               }
 
-               if (skb->len < sizeof(header))
-                       break;
+               data = skb_put(rx->ax_skb, rx->size);
+               memcpy(data, skb->data + offset, rx->size);
+               if (!remaining)
+                       usbnet_skb_return(dev, rx->ax_skb);
 
-               head = (u8 *) skb->data;
-               memcpy(&header, head, sizeof(header));
-               le32_to_cpus(&header);
-               packet = head + sizeof(header);
-               skb_pull(skb, 4);
+               offset += (rx->size + 1) & 0xfffe;
+               rx->size = remaining;
        }
 
-       if (skb->len < 0) {
-               netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d\n",
-                          skb->len);
+       if (skb->len != offset) {
+               netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d, %d\n",
+                          skb->len, offset);
                return 0;
        }
+
        return 1;
 }
 
+static int asix_rx_fixup_common(struct usbnet *dev, struct sk_buff *skb)
+{
+       struct asix_common_private *dp = dev->driver_priv;
+       struct asix_rx_fixup_info *rx = &dp->rx_fixup_info;
+
+       return asix_rx_fixup_internal(dev, skb, rx);
+}
+
 static struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
                                        gfp_t flags)
 {
@@ -1154,9 +1157,19 @@ static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
                dev->rx_urb_size = 2048;
        }
 
+       dev->driver_priv = kzalloc(sizeof(struct asix_common_private), GFP_KERNEL);
+       if (!dev->driver_priv)
+               return -ENOMEM;
+
        return 0;
 }
 
+static void ax88772_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+       if (dev->driver_priv)
+               kfree(dev->driver_priv);
+}
+
 static struct ethtool_ops ax88178_ethtool_ops = {
        .get_drvinfo            = asix_get_drvinfo,
        .get_link               = asix_get_link,
@@ -1489,6 +1502,10 @@ static int ax88178_bind(struct usbnet *dev, struct usb_interface *intf)
                dev->rx_urb_size = 2048;
        }
 
+       dev->driver_priv = kzalloc(sizeof(struct asix_common_private), GFP_KERNEL);
+       if (!dev->driver_priv)
+                       return -ENOMEM;
+
        return 0;
 }
 
@@ -1535,11 +1552,12 @@ static const struct driver_info hawking_uf200_info = {
 static const struct driver_info ax88772_info = {
        .description = "ASIX AX88772 USB 2.0 Ethernet",
        .bind = ax88772_bind,
+       .unbind = ax88772_unbind,
        .status = asix_status,
        .link_reset = ax88772_link_reset,
        .reset = ax88772_reset,
-       .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR,
-       .rx_fixup = asix_rx_fixup,
+       .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR | FLAG_MULTI_PACKET,
+       .rx_fixup = asix_rx_fixup_common,
        .tx_fixup = asix_tx_fixup,
 };
 
@@ -1557,11 +1575,13 @@ static const struct driver_info hg20f9_info = {
 static const struct driver_info ax88178_info = {
        .description = "ASIX AX88178 USB 2.0 Ethernet",
        .bind = ax88178_bind,
+       .unbind = ax88772_unbind,
        .status = asix_status,
        .link_reset = ax88178_link_reset,
        .reset = ax88178_reset,
-       .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR,
-       .rx_fixup = asix_rx_fixup,
+       .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR |
+                FLAG_MULTI_PACKET,
+       .rx_fixup = asix_rx_fixup_common,
        .tx_fixup = asix_tx_fixup,
 };