usb: gadget: f_ecm: Add suspend/resume and remote wakeup support
authorElson Roy Serrao <quic_eserrao@quicinc.com>
Fri, 24 Mar 2023 21:48:02 +0000 (14:48 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 29 Mar 2023 08:27:01 +0000 (10:27 +0200)
When host sends suspend notification to the device, handle
the suspend callbacks in the function driver. Depending on
the remote wakeup capability the device can either trigger a
remote wakeup or wait for the host initiated resume to resume
data transfer.

Signed-off-by: Elson Roy Serrao <quic_eserrao@quicinc.com>
Reviewed-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Link: https://lore.kernel.org/r/1679694482-16430-7-git-send-email-quic_eserrao@quicinc.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/gadget/function/f_ecm.c
drivers/usb/gadget/function/u_ether.c
drivers/usb/gadget/function/u_ether.h

index a7ab30e..c6e63ad 100644 (file)
@@ -885,6 +885,26 @@ static struct usb_function_instance *ecm_alloc_inst(void)
        return &opts->func_inst;
 }
 
+static void ecm_suspend(struct usb_function *f)
+{
+       struct f_ecm *ecm = func_to_ecm(f);
+       struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
+
+       DBG(cdev, "ECM Suspend\n");
+
+       gether_suspend(&ecm->port);
+}
+
+static void ecm_resume(struct usb_function *f)
+{
+       struct f_ecm *ecm = func_to_ecm(f);
+       struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
+
+       DBG(cdev, "ECM Resume\n");
+
+       gether_resume(&ecm->port);
+}
+
 static void ecm_free(struct usb_function *f)
 {
        struct f_ecm *ecm;
@@ -952,6 +972,8 @@ static struct usb_function *ecm_alloc(struct usb_function_instance *fi)
        ecm->port.func.setup = ecm_setup;
        ecm->port.func.disable = ecm_disable;
        ecm->port.func.free_func = ecm_free;
+       ecm->port.func.suspend = ecm_suspend;
+       ecm->port.func.resume = ecm_resume;
 
        return &ecm->port.func;
 }
index f259975..6956ad8 100644 (file)
@@ -437,6 +437,20 @@ static inline int is_promisc(u16 cdc_filter)
        return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS;
 }
 
+static int ether_wakeup_host(struct gether *port)
+{
+       int                     ret;
+       struct usb_function     *func = &port->func;
+       struct usb_gadget       *gadget = func->config->cdev->gadget;
+
+       if (func->func_suspended)
+               ret = usb_func_wakeup(func);
+       else
+               ret = usb_gadget_wakeup(gadget);
+
+       return ret;
+}
+
 static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
                                        struct net_device *net)
 {
@@ -456,6 +470,15 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
                in = NULL;
                cdc_filter = 0;
        }
+
+       if (dev->port_usb && dev->port_usb->is_suspend) {
+               DBG(dev, "Port suspended. Triggering wakeup\n");
+               netif_stop_queue(net);
+               spin_unlock_irqrestore(&dev->lock, flags);
+               ether_wakeup_host(dev->port_usb);
+               return NETDEV_TX_BUSY;
+       }
+
        spin_unlock_irqrestore(&dev->lock, flags);
 
        if (!in) {
@@ -1014,6 +1037,45 @@ int gether_set_ifname(struct net_device *net, const char *name, int len)
 }
 EXPORT_SYMBOL_GPL(gether_set_ifname);
 
+void gether_suspend(struct gether *link)
+{
+       struct eth_dev *dev = link->ioport;
+       unsigned long flags;
+
+       if (!dev)
+               return;
+
+       if (atomic_read(&dev->tx_qlen)) {
+               /*
+                * There is a transfer in progress. So we trigger a remote
+                * wakeup to inform the host.
+                */
+               ether_wakeup_host(dev->port_usb);
+               return;
+       }
+       spin_lock_irqsave(&dev->lock, flags);
+       link->is_suspend = true;
+       spin_unlock_irqrestore(&dev->lock, flags);
+}
+EXPORT_SYMBOL_GPL(gether_suspend);
+
+void gether_resume(struct gether *link)
+{
+       struct eth_dev *dev = link->ioport;
+       unsigned long flags;
+
+       if (!dev)
+               return;
+
+       if (netif_queue_stopped(dev->net))
+               netif_start_queue(dev->net);
+
+       spin_lock_irqsave(&dev->lock, flags);
+       link->is_suspend = false;
+       spin_unlock_irqrestore(&dev->lock, flags);
+}
+EXPORT_SYMBOL_GPL(gether_resume);
+
 /*
  * gether_cleanup - remove Ethernet-over-USB device
  * Context: may sleep
@@ -1176,6 +1238,7 @@ void gether_disconnect(struct gether *link)
 
        spin_lock(&dev->lock);
        dev->port_usb = NULL;
+       link->is_suspend = false;
        spin_unlock(&dev->lock);
 }
 EXPORT_SYMBOL_GPL(gether_disconnect);
index 4014454..851ee10 100644 (file)
@@ -79,6 +79,7 @@ struct gether {
        /* called on network open/close */
        void                            (*open)(struct gether *);
        void                            (*close)(struct gether *);
+       bool                            is_suspend;
 };
 
 #define        DEFAULT_FILTER  (USB_CDC_PACKET_TYPE_BROADCAST \
@@ -258,6 +259,9 @@ int gether_set_ifname(struct net_device *net, const char *name, int len);
 
 void gether_cleanup(struct eth_dev *dev);
 
+void gether_suspend(struct gether *link);
+void gether_resume(struct gether *link);
+
 /* connect/disconnect is handled by individual functions */
 struct net_device *gether_connect(struct gether *);
 void gether_disconnect(struct gether *);