]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/net/usb/usbnet.c
Merge remote-tracking branch 'usb/usb-next'
[karo-tx-linux.git] / drivers / net / usb / usbnet.c
index e4811d7b5af1cb19aad389f231781cede61c3f81..534b60b1056732efdecd931798d85784f00a2f31 100644 (file)
@@ -1232,6 +1232,37 @@ EXPORT_SYMBOL_GPL(usbnet_tx_timeout);
 
 /*-------------------------------------------------------------------------*/
 
+static int build_dma_sg(const struct sk_buff *skb, struct urb *urb)
+{
+       unsigned num_sgs, total_len = 0;
+       int i, s = 0;
+
+       num_sgs = skb_shinfo(skb)->nr_frags + 1;
+       if (num_sgs == 1)
+               return 0;
+
+       urb->sg = kmalloc(num_sgs * sizeof(struct scatterlist), GFP_ATOMIC);
+       if (!urb->sg)
+               return -ENOMEM;
+
+       urb->num_sgs = num_sgs;
+       sg_init_table(urb->sg, urb->num_sgs);
+
+       sg_set_buf(&urb->sg[s++], skb->data, skb_headlen(skb));
+       total_len += skb_headlen(skb);
+
+       for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+               struct skb_frag_struct *f = &skb_shinfo(skb)->frags[i];
+
+               total_len += skb_frag_size(f);
+               sg_set_page(&urb->sg[i + s], f->page.p, f->size,
+                               f->page_offset);
+       }
+       urb->transfer_buffer_length = total_len;
+
+       return 1;
+}
+
 netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
                                     struct net_device *net)
 {
@@ -1258,7 +1289,6 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
                        goto drop;
                }
        }
-       length = skb->len;
 
        if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
                netif_dbg(dev, tx_err, dev->net, "no urb\n");
@@ -1268,10 +1298,14 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
        entry = (struct skb_data *) skb->cb;
        entry->urb = urb;
        entry->dev = dev;
-       entry->length = length;
 
        usb_fill_bulk_urb (urb, dev->udev, dev->out,
                        skb->data, skb->len, tx_complete, skb);
+       if (dev->can_dma_sg) {
+               if (build_dma_sg(skb, urb) < 0)
+                       goto drop;
+       }
+       entry->length = length = urb->transfer_buffer_length;
 
        /* don't assume the hardware handles USB_ZERO_PACKET
         * NOTE:  strictly conforming cdc-ether devices should expect
@@ -1340,7 +1374,10 @@ drop:
 not_drop:
                if (skb)
                        dev_kfree_skb_any (skb);
-               usb_free_urb (urb);
+               if (urb) {
+                       kfree(urb->sg);
+                       usb_free_urb(urb);
+               }
        } else
                netif_dbg(dev, tx_queued, dev->net,
                          "> tx, len %d, type 0x%x\n", length, skb->protocol);
@@ -1391,6 +1428,7 @@ static void usbnet_bh (unsigned long param)
                        rx_process (dev, skb);
                        continue;
                case tx_done:
+                       kfree(entry->urb->sg);
                case rx_cleanup:
                        usb_free_urb (entry->urb);
                        dev_kfree_skb (skb);
@@ -1727,6 +1765,7 @@ int usbnet_resume (struct usb_interface *intf)
                        retval = usb_submit_urb(res, GFP_ATOMIC);
                        if (retval < 0) {
                                dev_kfree_skb_any(skb);
+                               kfree(res->sg);
                                usb_free_urb(res);
                                usb_autopm_put_interface_async(dev->intf);
                        } else {