[PATCH] WIP: Suspend getherlink on system suspend
From: John Ernberg
Date: Mon May 05 2025 - 03:09:50 EST
---
drivers/usb/gadget/composite.c | 68 +++++++++++++++++++++++++++
drivers/usb/gadget/configfs.c | 53 +++++++++++++++++++++
drivers/usb/gadget/function/f_ecm.c | 22 +++++++++
drivers/usb/gadget/function/u_ether.c | 34 ++++++++++++++
drivers/usb/gadget/function/u_ether.h | 2 +
drivers/usb/gadget/udc/core.c | 29 ++++++++++++
include/linux/usb/composite.h | 4 ++
include/linux/usb/gadget.h | 2 +
8 files changed, 214 insertions(+)
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.=
c
index 8402a86176f4..f1ed1db1e1d0 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -2669,6 +2669,72 @@ void composite_resume(struct usb_gadget *gadget)
cdev->suspended =3D 0;
}
=20
+int composite_system_suspend(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev =3D get_gadget_data(gadget);
+ struct usb_function *f;
+ int ret;
+
+ DBG(cdev, "system suspend\n");
+ if (cdev->config) {
+ list_for_each_entry(f, &cdev->config->functions, list) {
+ if (f->system_suspend) {
+ ret =3D f->system_suspend(f);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ if (cdev->config &&
+ cdev->config->bmAttributes & USB_CONFIG_ATT_SELFPOWER)
+ usb_gadget_set_selfpowered(gadget);
+
+ usb_gadget_vbus_draw(gadget, 2);
+
+ return 0;
+}
+
+int composite_system_resume(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev =3D get_gadget_data(gadget);
+ struct usb_function *f;
+ unsigned maxpower;
+ int ret;
+
+ DBG(cdev, "system resume\n");
+ if (cdev->config) {
+ list_for_each_entry(f, &cdev->config->functions, list) {
+ if (f->system_resume) {
+ ret =3D f->system_resume(f);
+ if (ret)
+ return ret;
+ }
+ }
+
+ maxpower =3D cdev->config->MaxPower ?
+ cdev->config->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW;
+ if (gadget->speed < USB_SPEED_SUPER)
+ maxpower =3D min(maxpower, 500U);
+ else
+ maxpower =3D min(maxpower, 900U);
+
+ if (maxpower > USB_SELF_POWER_VBUS_MAX_DRAW ||
+ !(cdev->config->bmAttributes & USB_CONFIG_ATT_SELFPOWER))
+ usb_gadget_clear_selfpowered(gadget);
+ else
+ usb_gadget_set_selfpowered(gadget);
+
+ usb_gadget_vbus_draw(gadget, maxpower);
+ } else {
+ maxpower =3D CONFIG_USB_GADGET_VBUS_DRAW;
+ maxpower =3D min(maxpower, 100U);
+ usb_gadget_vbus_draw(gadget, maxpower);
+ }
+
+ return 0;
+}
+
/*------------------------------------------------------------------------=
-*/
=20
static const struct usb_gadget_driver composite_driver_template =3D {
@@ -2681,6 +2747,8 @@ static const struct usb_gadget_driver composite_drive=
r_template =3D {
=20
.suspend =3D composite_suspend,
.resume =3D composite_resume,
+ .system_suspend =3D composite_system_suspend,
+ .system_resume =3D composite_system_resume,
=20
.driver =3D {
.owner =3D THIS_MODULE,
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 29390d573e23..e0d2f0998e86 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -1962,6 +1962,57 @@ static void configfs_composite_resume(struct usb_gad=
get *gadget)
spin_unlock_irqrestore(&gi->spinlock, flags);
}
=20
+static int configfs_composite_system_suspend(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev;
+ struct gadget_info *gi;
+ unsigned long flags;
+ int ret;
+
+ cdev =3D get_gadget_data(gadget);
+ if (!cdev)
+ return 0;
+
+ gi =3D container_of(cdev, struct gadget_info, cdev);
+ spin_lock_irqsave(&gi->spinlock, flags);
+ cdev =3D get_gadget_data(gadget);
+ if (!cdev || gi->unbind) {
+ spin_unlock_irqrestore(&gi->spinlock, flags);
+ return 0;
+ }
+
+ ret =3D composite_system_suspend(gadget);
+ spin_unlock_irqrestore(&gi->spinlock, flags);
+
+ return ret;
+}
+
+static int configfs_composite_system_resume(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev;
+ struct gadget_info *gi;
+ unsigned long flags;
+ int ret;
+
+ cdev =3D get_gadget_data(gadget);
+ if (!cdev)
+ return 0;
+
+ gi =3D container_of(cdev, struct gadget_info, cdev);
+ spin_lock_irqsave(&gi->spinlock, flags);
+ cdev =3D get_gadget_data(gadget);
+ if (!cdev || gi->unbind) {
+ spin_unlock_irqrestore(&gi->spinlock, flags);
+ return 0;
+ }
+
+ ret =3D composite_system_resume(gadget);
+ spin_unlock_irqrestore(&gi->spinlock, flags);
+
+ return ret;
+}
+
+
static const struct usb_gadget_driver configfs_driver_template =3D {
.bind =3D configfs_composite_bind,
.unbind =3D configfs_composite_unbind,
@@ -1972,6 +2023,8 @@ static const struct usb_gadget_driver configfs_driver=
_template =3D {
=20
.suspend =3D configfs_composite_suspend,
.resume =3D configfs_composite_resume,
+ .system_suspend =3D configfs_composite_system_suspend,
+ .system_resume =3D configfs_composite_system_resume,
=20
.max_speed =3D USB_SPEED_SUPER_PLUS,
.driver =3D {
diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/funct=
ion/f_ecm.c
index 6cb7771e8a69..4df67d5ee0fa 100644
--- a/drivers/usb/gadget/function/f_ecm.c
+++ b/drivers/usb/gadget/function/f_ecm.c
@@ -892,6 +892,26 @@ static void ecm_resume(struct usb_function *f)
gether_resume(&ecm->port);
}
=20
+static int ecm_system_suspend(struct usb_function *f)
+{
+ struct f_ecm *ecm =3D func_to_ecm(f);
+ struct usb_composite_dev *cdev =3D ecm->port.func.config->cdev;
+
+ DBG(cdev, "ECM System Suspend\n");
+
+ return gether_system_suspend(&ecm->port);
+}
+
+static int ecm_system_resume(struct usb_function *f)
+{
+ struct f_ecm *ecm =3D func_to_ecm(f);
+ struct usb_composite_dev *cdev =3D ecm->port.func.config->cdev;
+
+ DBG(cdev, "ECM System Resume\n");
+
+ return gether_system_resume(&ecm->port);
+}
+
static void ecm_free(struct usb_function *f)
{
struct f_ecm *ecm;
@@ -961,6 +981,8 @@ static struct usb_function *ecm_alloc(struct usb_functi=
on_instance *fi)
ecm->port.func.free_func =3D ecm_free;
ecm->port.func.suspend =3D ecm_suspend;
ecm->port.func.resume =3D ecm_resume;
+ ecm->port.func.system_suspend =3D ecm_system_suspend;
+ ecm->port.func.system_resume =3D ecm_system_resume;
=20
return &ecm->port.func;
}
diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/fun=
ction/u_ether.c
index f58590bf5e02..d4f0e28ffd4d 100644
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -1078,6 +1078,40 @@ void gether_resume(struct gether *link)
}
EXPORT_SYMBOL_GPL(gether_resume);
=20
+int gether_system_suspend(struct gether *link)
+{
+ struct eth_dev *dev =3D link->ioport;
+ struct net_device *ndev =3D dev->net;
+
+ rtnl_lock();
+ if (netif_running(ndev)) {
+ netif_tx_lock_bh(ndev);
+ netif_device_detach(ndev);
+ netif_tx_unlock_bh(ndev);
+ }
+ rtnl_unlock();
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gether_system_suspend);
+
+int gether_system_resume(struct gether *link)
+{
+ struct eth_dev *dev =3D link->ioport;
+ struct net_device *ndev =3D dev->net;
+
+ rtnl_lock();
+ if (netif_running(ndev)) {
+ netif_tx_lock_bh(ndev);
+ netif_device_attach(ndev);
+ netif_tx_unlock_bh(ndev);
+ }
+ rtnl_unlock();
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gether_system_resume);
+
/*
* gether_cleanup - remove Ethernet-over-USB device
* Context: may sleep
diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/fun=
ction/u_ether.h
index 34be220cef77..ffd023b7be7b 100644
--- a/drivers/usb/gadget/function/u_ether.h
+++ b/drivers/usb/gadget/function/u_ether.h
@@ -261,6 +261,8 @@ void gether_cleanup(struct eth_dev *dev);
=20
void gether_suspend(struct gether *link);
void gether_resume(struct gether *link);
+int gether_system_suspend(struct gether *link);
+int gether_system_resume(struct gether *link);
=20
/* connect/disconnect is handled by individual functions */
struct net_device *gether_connect(struct gether *);
diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index 4b3d5075621a..1e4ee5ffcfbf 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -1683,6 +1683,30 @@ static void gadget_unbind_driver(struct device *dev)
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
}
=20
+static int gadget_suspend_driver(struct device *dev)
+{
+ struct usb_gadget *gadget =3D dev_to_usb_gadget(dev);
+ struct usb_udc *udc =3D gadget->udc;
+ struct usb_gadget_driver *driver =3D udc->driver;
+
+ if (driver->system_suspend)
+ return driver->system_suspend(gadget);
+
+ return 0;
+}
+
+static int gadget_resume_driver(struct device *dev)
+{
+ struct usb_gadget *gadget =3D dev_to_usb_gadget(dev);
+ struct usb_udc *udc =3D gadget->udc;
+ struct usb_gadget_driver *driver =3D udc->driver;
+
+ if (driver->system_resume)
+ return driver->system_resume(gadget);
+
+ return 0;
+}
+
/* -----------------------------------------------------------------------=
-- */
=20
int usb_gadget_register_driver_owner(struct usb_gadget_driver *driver,
@@ -1896,11 +1920,16 @@ static const struct class udc_class =3D {
.dev_uevent =3D usb_udc_uevent,
};
=20
+static const struct dev_pm_ops gadget_bus_pm_ops =3D {
+ SET_SYSTEM_SLEEP_PM_OPS(gadget_suspend_driver, gadget_resume_driver)
+};
+
static const struct bus_type gadget_bus_type =3D {
.name =3D "gadget",
.probe =3D gadget_bind_driver,
.remove =3D gadget_unbind_driver,
.match =3D gadget_match_driver,
+ .pm =3D &gadget_bus_pm_ops,
};
=20
static int __init usb_udc_init(void)
diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
index 6e38fb9d2117..f42ba1cfd181 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -226,6 +226,8 @@ struct usb_function {
bool config0);
void (*suspend)(struct usb_function *);
void (*resume)(struct usb_function *);
+ int (*system_suspend)(struct usb_function *);
+ int (*system_resume)(struct usb_function *);
=20
/* USB 3.0 additions */
int (*get_status)(struct usb_function *);
@@ -522,6 +524,8 @@ extern int composite_setup(struct usb_gadget *gadget,
const struct usb_ctrlrequest *ctrl);
extern void composite_suspend(struct usb_gadget *gadget);
extern void composite_resume(struct usb_gadget *gadget);
+extern int composite_system_suspend(struct usb_gadget *gadget);
+extern int composite_system_resume(struct usb_gadget *gadget);
=20
/*
* Some systems will need runtime overrides for the product identifiers
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index df33333650a0..8cdfdece1561 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -744,6 +744,8 @@ struct usb_gadget_driver {
void (*disconnect)(struct usb_gadget *);
void (*suspend)(struct usb_gadget *);
void (*resume)(struct usb_gadget *);
+ int (*system_suspend)(struct usb_gadget *);
+ int (*system_resume)(struct usb_gadget *);
void (*reset)(struct usb_gadget *);
=20
/* FIXME support safe rmmod */=