Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[firefly-linux-kernel-4.4.55.git] / drivers / net / usb / usbnet.c
index edb81ed06950624a182ad1e298f2f54d142804e7..51f3192f3931e0a179dcb619e4e714a0565656fc 100644 (file)
@@ -380,6 +380,12 @@ static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
        unsigned long           lockflags;
        size_t                  size = dev->rx_urb_size;
 
+       /* prevent rx skb allocation when error ratio is high */
+       if (test_bit(EVENT_RX_KILL, &dev->flags)) {
+               usb_free_urb(urb);
+               return -ENOLINK;
+       }
+
        skb = __netdev_alloc_skb_ip_align(dev->net, size, flags);
        if (!skb) {
                netif_dbg(dev, rx_err, dev->net, "no rx skb\n");
@@ -539,6 +545,17 @@ block:
                break;
        }
 
+       /* stop rx if packet error rate is high */
+       if (++dev->pkt_cnt > 30) {
+               dev->pkt_cnt = 0;
+               dev->pkt_err = 0;
+       } else {
+               if (state == rx_cleanup)
+                       dev->pkt_err++;
+               if (dev->pkt_err > 20)
+                       set_bit(EVENT_RX_KILL, &dev->flags);
+       }
+
        state = defer_bh(dev, skb, &dev->rxq, state);
 
        if (urb) {
@@ -719,7 +736,8 @@ int usbnet_stop (struct net_device *net)
        dev->flags = 0;
        del_timer_sync (&dev->delay);
        tasklet_kill (&dev->bh);
-       if (info->manage_power)
+       if (info->manage_power &&
+           !test_and_clear_bit(EVENT_NO_RUNTIME_PM, &dev->flags))
                info->manage_power(dev, 0);
        else
                usb_autopm_put_interface(dev->intf);
@@ -790,18 +808,23 @@ int usbnet_open (struct net_device *net)
                   (dev->driver_info->flags & FLAG_FRAMING_AX) ? "ASIX" :
                   "simple");
 
+       /* reset rx error state */
+       dev->pkt_cnt = 0;
+       dev->pkt_err = 0;
+       clear_bit(EVENT_RX_KILL, &dev->flags);
+
        // delay posting reads until we're fully open
        tasklet_schedule (&dev->bh);
        if (info->manage_power) {
                retval = info->manage_power(dev, 1);
-               if (retval < 0)
-                       goto done_manage_power_error;
-               usb_autopm_put_interface(dev->intf);
+               if (retval < 0) {
+                       retval = 0;
+                       set_bit(EVENT_NO_RUNTIME_PM, &dev->flags);
+               } else {
+                       usb_autopm_put_interface(dev->intf);
+               }
        }
        return retval;
-
-done_manage_power_error:
-       clear_bit(EVENT_DEV_OPEN, &dev->flags);
 done:
        usb_autopm_put_interface(dev->intf);
 done_nopm:
@@ -1102,13 +1125,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
        if (info->tx_fixup) {
                skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
                if (!skb) {
-                       if (netif_msg_tx_err(dev)) {
-                               netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
-                               goto drop;
-                       } else {
-                               /* cdc_ncm collected packet; waits for more */
+                       /* packet collected; minidriver waiting for more */
+                       if (info->flags & FLAG_MULTI_PACKET)
                                goto not_drop;
-                       }
+                       netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+                       goto drop;
                }
        }
        length = skb->len;
@@ -1253,6 +1274,9 @@ static void usbnet_bh (unsigned long param)
                }
        }
 
+       /* restart RX again after disabling due to high error rate */
+       clear_bit(EVENT_RX_KILL, &dev->flags);
+
        // waiting for all pending urbs to complete?
        if (dev->wait) {
                if ((dev->txq.qlen + dev->rxq.qlen + dev->done.qlen) == 0) {
@@ -1447,6 +1471,10 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
                if ((dev->driver_info->flags & FLAG_WWAN) != 0)
                        strcpy(net->name, "wwan%d");
 
+               /* devices that cannot do ARP */
+               if ((dev->driver_info->flags & FLAG_NOARP) != 0)
+                       net->flags |= IFF_NOARP;
+
                /* maybe the remote can't receive an Ethernet MTU */
                if (net->mtu > (dev->hard_mtu - net->hard_header_len))
                        net->mtu = dev->hard_mtu - net->hard_header_len;
@@ -1615,6 +1643,209 @@ void usbnet_device_suggests_idle(struct usbnet *dev)
 }
 EXPORT_SYMBOL(usbnet_device_suggests_idle);
 
+/*
+ * For devices that can do without special commands
+ */
+int usbnet_manage_power(struct usbnet *dev, int on)
+{
+       dev->intf->needs_remote_wakeup = on;
+       return 0;
+}
+EXPORT_SYMBOL(usbnet_manage_power);
+
+/*-------------------------------------------------------------------------*/
+static int __usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype,
+                            u16 value, u16 index, void *data, u16 size)
+{
+       void *buf = NULL;
+       int err = -ENOMEM;
+
+       netdev_dbg(dev->net, "usbnet_read_cmd cmd=0x%02x reqtype=%02x"
+                  " value=0x%04x index=0x%04x size=%d\n",
+                  cmd, reqtype, value, index, size);
+
+       if (data) {
+               buf = kmalloc(size, GFP_KERNEL);
+               if (!buf)
+                       goto out;
+       }
+
+       err = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+                             cmd, reqtype, value, index, buf, size,
+                             USB_CTRL_GET_TIMEOUT);
+       if (err > 0 && err <= size)
+               memcpy(data, buf, err);
+       kfree(buf);
+out:
+       return err;
+}
+
+static int __usbnet_write_cmd(struct usbnet *dev, u8 cmd, u8 reqtype,
+                             u16 value, u16 index, const void *data,
+                             u16 size)
+{
+       void *buf = NULL;
+       int err = -ENOMEM;
+
+       netdev_dbg(dev->net, "usbnet_write_cmd cmd=0x%02x reqtype=%02x"
+                  " value=0x%04x index=0x%04x size=%d\n",
+                  cmd, reqtype, value, index, size);
+
+       if (data) {
+               buf = kmemdup(data, size, GFP_KERNEL);
+               if (!buf)
+                       goto out;
+       }
+
+       err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+                             cmd, reqtype, value, index, buf, size,
+                             USB_CTRL_SET_TIMEOUT);
+       kfree(buf);
+
+out:
+       return err;
+}
+
+/*
+ * The function can't be called inside suspend/resume callback,
+ * otherwise deadlock will be caused.
+ */
+int usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype,
+                   u16 value, u16 index, void *data, u16 size)
+{
+       int ret;
+
+       if (usb_autopm_get_interface(dev->intf) < 0)
+               return -ENODEV;
+       ret = __usbnet_read_cmd(dev, cmd, reqtype, value, index,
+                               data, size);
+       usb_autopm_put_interface(dev->intf);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(usbnet_read_cmd);
+
+/*
+ * The function can't be called inside suspend/resume callback,
+ * otherwise deadlock will be caused.
+ */
+int usbnet_write_cmd(struct usbnet *dev, u8 cmd, u8 reqtype,
+                    u16 value, u16 index, const void *data, u16 size)
+{
+       int ret;
+
+       if (usb_autopm_get_interface(dev->intf) < 0)
+               return -ENODEV;
+       ret = __usbnet_write_cmd(dev, cmd, reqtype, value, index,
+                                data, size);
+       usb_autopm_put_interface(dev->intf);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(usbnet_write_cmd);
+
+/*
+ * The function can be called inside suspend/resume callback safely
+ * and should only be called by suspend/resume callback generally.
+ */
+int usbnet_read_cmd_nopm(struct usbnet *dev, u8 cmd, u8 reqtype,
+                         u16 value, u16 index, void *data, u16 size)
+{
+       return __usbnet_read_cmd(dev, cmd, reqtype, value, index,
+                                data, size);
+}
+EXPORT_SYMBOL_GPL(usbnet_read_cmd_nopm);
+
+/*
+ * The function can be called inside suspend/resume callback safely
+ * and should only be called by suspend/resume callback generally.
+ */
+int usbnet_write_cmd_nopm(struct usbnet *dev, u8 cmd, u8 reqtype,
+                         u16 value, u16 index, const void *data,
+                         u16 size)
+{
+       return __usbnet_write_cmd(dev, cmd, reqtype, value, index,
+                                 data, size);
+}
+EXPORT_SYMBOL_GPL(usbnet_write_cmd_nopm);
+
+static void usbnet_async_cmd_cb(struct urb *urb)
+{
+       struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
+       int status = urb->status;
+
+       if (status < 0)
+               dev_dbg(&urb->dev->dev, "%s failed with %d",
+                       __func__, status);
+
+       kfree(req);
+       usb_free_urb(urb);
+}
+
+/*
+ * The caller must make sure that device can't be put into suspend
+ * state until the control URB completes.
+ */
+int usbnet_write_cmd_async(struct usbnet *dev, u8 cmd, u8 reqtype,
+                          u16 value, u16 index, const void *data, u16 size)
+{
+       struct usb_ctrlrequest *req = NULL;
+       struct urb *urb;
+       int err = -ENOMEM;
+       void *buf = NULL;
+
+       netdev_dbg(dev->net, "usbnet_write_cmd cmd=0x%02x reqtype=%02x"
+                  " value=0x%04x index=0x%04x size=%d\n",
+                  cmd, reqtype, value, index, size);
+
+       urb = usb_alloc_urb(0, GFP_ATOMIC);
+       if (!urb) {
+               netdev_err(dev->net, "Error allocating URB in"
+                          " %s!\n", __func__);
+               goto fail;
+       }
+
+       if (data) {
+               buf = kmemdup(data, size, GFP_ATOMIC);
+               if (!buf) {
+                       netdev_err(dev->net, "Error allocating buffer"
+                                  " in %s!\n", __func__);
+                       goto fail_free;
+               }
+       }
+
+       req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC);
+       if (!req)
+               goto fail_free_buf;
+
+       req->bRequestType = reqtype;
+       req->bRequest = cmd;
+       req->wValue = cpu_to_le16(value);
+       req->wIndex = cpu_to_le16(index);
+       req->wLength = cpu_to_le16(size);
+
+       usb_fill_control_urb(urb, dev->udev,
+                            usb_sndctrlpipe(dev->udev, 0),
+                            (void *)req, buf, size,
+                            usbnet_async_cmd_cb, req);
+       urb->transfer_flags |= URB_FREE_BUFFER;
+
+       err = usb_submit_urb(urb, GFP_ATOMIC);
+       if (err < 0) {
+               netdev_err(dev->net, "Error submitting the control"
+                          " message: status=%d\n", err);
+               goto fail_free;
+       }
+       return 0;
+
+fail_free_buf:
+       kfree(buf);
+fail_free:
+       kfree(req);
+       usb_free_urb(urb);
+fail:
+       return err;
+
+}
+EXPORT_SYMBOL_GPL(usbnet_write_cmd_async);
 /*-------------------------------------------------------------------------*/
 
 static int __init usbnet_init(void)