diff options
author | Lucas Stach <dev@lynxeye.de> | 2013-01-16 08:24:07 +0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-01-18 23:13:29 +0400 |
commit | 8b5b6f5413e97c3e8bafcdd67553d508f4f698cd (patch) | |
tree | fb90f82d9232e927f0656f3cb9cb0226e030c31c /drivers/net/usb/asix_common.c | |
parent | 5620df65d81292c5fb1beba8d380ef58cd98b53f (diff) | |
download | linux-8b5b6f5413e97c3e8bafcdd67553d508f4f698cd.tar.xz |
net: asix: handle packets crossing URB boundaries
ASIX AX88772B started to pack data even more tightly. Packets and the ASIX packet
header may now cross URB boundaries. To handle this we have to introduce
some state between individual calls to asix_rx_fixup().
Signed-off-by: Lucas Stach <dev@lynxeye.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/usb/asix_common.c')
-rw-r--r-- | drivers/net/usb/asix_common.c | 90 |
1 files changed, 65 insertions, 25 deletions
diff --git a/drivers/net/usb/asix_common.c b/drivers/net/usb/asix_common.c index 19bc23f20526..f7f623a5390e 100644 --- a/drivers/net/usb/asix_common.c +++ b/drivers/net/usb/asix_common.c @@ -51,49 +51,89 @@ void asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, value, index, data, size); } -int asix_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +int asix_rx_fixup_internal(struct usbnet *dev, struct sk_buff *skb, + struct asix_rx_fixup_info *rx) { int offset = 0; - while (offset + sizeof(u32) < skb->len) { - struct sk_buff *ax_skb; - u16 size; - u32 header = get_unaligned_le32(skb->data + offset); - - offset += sizeof(u32); - - /* get the packet length */ - size = (u16) (header & 0x7ff); - if (size != ((~header >> 16) & 0x07ff)) { - netdev_err(dev->net, "asix_rx_fixup() Bad Header Length\n"); - return 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); + } + + /* 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; + } + 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) || - (size + offset > skb->len)) { + if (rx->size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) { netdev_err(dev->net, "asix_rx_fixup() Bad RX Length %d\n", - size); + rx->size); + kfree_skb(rx->ax_skb); return 0; } - ax_skb = netdev_alloc_skb_ip_align(dev->net, size); - if (!ax_skb) - return 0; - skb_put(ax_skb, size); - memcpy(ax_skb->data, skb->data + offset, size); - usbnet_skb_return(dev, ax_skb); + if (rx->size > skb->len - offset) { + remaining = rx->size - (skb->len - offset); + rx->size = skb->len - offset; + } + + data = skb_put(rx->ax_skb, rx->size); + memcpy(data, skb->data + offset, rx->size); + if (!remaining) + usbnet_skb_return(dev, rx->ax_skb); - offset += (size + 1) & 0xfffe; + offset += (rx->size + 1) & 0xfffe; + rx->size = remaining; } if (skb->len != offset) { - netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d\n", - skb->len); + netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d, %d\n", + skb->len, offset); return 0; } + return 1; } +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); +} + struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) { |