cx82310_eth: re-enable ethernet mode after router reboot
authorOndrej Zary <linux@zary.sk>
Sat, 10 Oct 2020 14:00:46 +0000 (16:00 +0200)
committerJakub Kicinski <kuba@kernel.org>
Mon, 12 Oct 2020 16:46:40 +0000 (09:46 -0700)
When the router is rebooted without a power cycle, the USB device
remains connected but its configuration is reset. This results in
a non-working ethernet connection with messages like this in syslog:
usb 2-2: RX packet too long: 65535 B

Re-enable ethernet mode when receiving a packet with invalid size of
0xffff.

Signed-off-by: Ondrej Zary <linux@zary.sk>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/usb/cx82310_eth.c

index 32b08b1..0436793 100644 (file)
@@ -40,6 +40,11 @@ enum cx82310_status {
 #define CX82310_MTU    1514
 #define CMD_EP         0x01
 
+struct cx82310_priv {
+       struct work_struct reenable_work;
+       struct usbnet *dev;
+};
+
 /*
  * execute control command
  *  - optionally send some data (command parameters)
@@ -115,6 +120,23 @@ end:
        return ret;
 }
 
+static int cx82310_enable_ethernet(struct usbnet *dev)
+{
+       int ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
+
+       if (ret)
+               netdev_err(dev->net, "unable to enable ethernet mode: %d\n",
+                          ret);
+       return ret;
+}
+
+static void cx82310_reenable_work(struct work_struct *work)
+{
+       struct cx82310_priv *priv = container_of(work, struct cx82310_priv,
+                                                reenable_work);
+       cx82310_enable_ethernet(priv->dev);
+}
+
 #define partial_len    data[0]         /* length of partial packet data */
 #define partial_rem    data[1]         /* remaining (missing) data length */
 #define partial_data   data[2]         /* partial packet data */
@@ -126,6 +148,7 @@ static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
        struct usb_device *udev = dev->udev;
        u8 link[3];
        int timeout = 50;
+       struct cx82310_priv *priv;
 
        /* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */
        if (usb_string(udev, udev->descriptor.iProduct, buf, sizeof(buf)) > 0
@@ -152,6 +175,15 @@ static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
        if (!dev->partial_data)
                return -ENOMEM;
 
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv) {
+               ret = -ENOMEM;
+               goto err_partial;
+       }
+       dev->driver_priv = priv;
+       INIT_WORK(&priv->reenable_work, cx82310_reenable_work);
+       priv->dev = dev;
+
        /* wait for firmware to become ready (indicated by the link being up) */
        while (--timeout) {
                ret = cx82310_cmd(dev, CMD_GET_LINK_STATUS, true, NULL, 0,
@@ -168,12 +200,8 @@ static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
        }
 
        /* enable ethernet mode (?) */
-       ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
-       if (ret) {
-               dev_err(&udev->dev, "unable to enable ethernet mode: %d\n",
-                       ret);
+       if (cx82310_enable_ethernet(dev))
                goto err;
-       }
 
        /* get the MAC address */
        ret = cx82310_cmd(dev, CMD_GET_MAC_ADDR, true, NULL, 0,
@@ -190,13 +218,19 @@ static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
 
        return 0;
 err:
+       kfree(dev->driver_priv);
+err_partial:
        kfree((void *)dev->partial_data);
        return ret;
 }
 
 static void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf)
 {
+       struct cx82310_priv *priv = dev->driver_priv;
+
        kfree((void *)dev->partial_data);
+       cancel_work_sync(&priv->reenable_work);
+       kfree(dev->driver_priv);
 }
 
 /*
@@ -211,6 +245,7 @@ static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
 {
        int len;
        struct sk_buff *skb2;
+       struct cx82310_priv *priv = dev->driver_priv;
 
        /*
         * If the last skb ended with an incomplete packet, this skb contains
@@ -245,7 +280,10 @@ static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
                        break;
                }
 
-               if (len > CX82310_MTU) {
+               if (len == 0xffff) {
+                       netdev_info(dev->net, "router was rebooted, re-enabling ethernet mode");
+                       schedule_work(&priv->reenable_work);
+               } else if (len > CX82310_MTU) {
                        dev_err(&dev->udev->dev, "RX packet too long: %d B\n",
                                len);
                        return 0;