mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-29 15:14:18 +08:00
usb: hub: port: add sysfs entry to switch port power
In some cases the port of an hub needs to be disabled or switched off and on again. E.g. when the connected device needs to be re-enumerated. Or it needs to be explicitly disabled while the rest of the usb tree stays working. For this purpose this patch adds an sysfs switch to enable/disable the port on any hub. In the case the hub is supporting power switching, the power line will be disabled to the connected device. When the port gets disabled, the associated device gets disconnected and removed from the logical usb tree. No further device will be enumerated on that port until the port gets enabled again. Reviewed-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de> Link: https://lore.kernel.org/r/20220607114522.3359148-1-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
5fd6c4f0a6
commit
f061f43d74
@ -253,6 +253,17 @@ Description:
|
|||||||
only if the system firmware is capable of describing the
|
only if the system firmware is capable of describing the
|
||||||
connection between a port and its connector.
|
connection between a port and its connector.
|
||||||
|
|
||||||
|
What: /sys/bus/usb/devices/.../<hub_interface>/port<X>/disable
|
||||||
|
Date: June 2022
|
||||||
|
Contact: Michael Grzeschik <m.grzeschik@pengutronix.de>
|
||||||
|
Description:
|
||||||
|
This file controls the state of a USB port, including
|
||||||
|
Vbus power output (but only on hubs that support
|
||||||
|
power switching -- most hubs don't support it). If
|
||||||
|
a port is disabled, the port is unusable: Devices
|
||||||
|
attached to the port will not be detected, initialized,
|
||||||
|
or enumerated.
|
||||||
|
|
||||||
What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout
|
What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout
|
||||||
Date: May 2013
|
Date: May 2013
|
||||||
Contact: Mathias Nyman <mathias.nyman@linux.intel.com>
|
Contact: Mathias Nyman <mathias.nyman@linux.intel.com>
|
||||||
|
@ -613,7 +613,7 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hub_port_status(struct usb_hub *hub, int port1,
|
int usb_hub_port_status(struct usb_hub *hub, int port1,
|
||||||
u16 *status, u16 *change)
|
u16 *status, u16 *change)
|
||||||
{
|
{
|
||||||
return hub_ext_port_status(hub, port1, HUB_PORT_STATUS,
|
return hub_ext_port_status(hub, port1, HUB_PORT_STATUS,
|
||||||
@ -1126,7 +1126,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
|
|||||||
u16 portstatus, portchange;
|
u16 portstatus, portchange;
|
||||||
|
|
||||||
portstatus = portchange = 0;
|
portstatus = portchange = 0;
|
||||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
|
||||||
if (status)
|
if (status)
|
||||||
goto abort;
|
goto abort;
|
||||||
|
|
||||||
@ -2855,7 +2855,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
|
|||||||
&portstatus, &portchange,
|
&portstatus, &portchange,
|
||||||
&ext_portstatus);
|
&ext_portstatus);
|
||||||
else
|
else
|
||||||
ret = hub_port_status(hub, port1, &portstatus,
|
ret = usb_hub_port_status(hub, port1, &portstatus,
|
||||||
&portchange);
|
&portchange);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
@ -2956,7 +2956,8 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|||||||
* If the caller hasn't explicitly requested a warm reset,
|
* If the caller hasn't explicitly requested a warm reset,
|
||||||
* double check and see if one is needed.
|
* double check and see if one is needed.
|
||||||
*/
|
*/
|
||||||
if (hub_port_status(hub, port1, &portstatus, &portchange) == 0)
|
if (usb_hub_port_status(hub, port1, &portstatus,
|
||||||
|
&portchange) == 0)
|
||||||
if (hub_port_warm_reset_required(hub, port1,
|
if (hub_port_warm_reset_required(hub, port1,
|
||||||
portstatus))
|
portstatus))
|
||||||
warm = true;
|
warm = true;
|
||||||
@ -3008,7 +3009,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|||||||
* If a USB 3.0 device migrates from reset to an error
|
* If a USB 3.0 device migrates from reset to an error
|
||||||
* state, re-issue the warm reset.
|
* state, re-issue the warm reset.
|
||||||
*/
|
*/
|
||||||
if (hub_port_status(hub, port1,
|
if (usb_hub_port_status(hub, port1,
|
||||||
&portstatus, &portchange) < 0)
|
&portstatus, &portchange) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
@ -3074,7 +3075,7 @@ done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a port is power on */
|
/* Check if a port is power on */
|
||||||
static int port_is_power_on(struct usb_hub *hub, unsigned portstatus)
|
int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
@ -3140,13 +3141,13 @@ static int check_port_resume_type(struct usb_device *udev,
|
|||||||
}
|
}
|
||||||
/* Is the device still present? */
|
/* Is the device still present? */
|
||||||
else if (status || port_is_suspended(hub, portstatus) ||
|
else if (status || port_is_suspended(hub, portstatus) ||
|
||||||
!port_is_power_on(hub, portstatus)) {
|
!usb_port_is_power_on(hub, portstatus)) {
|
||||||
if (status >= 0)
|
if (status >= 0)
|
||||||
status = -ENODEV;
|
status = -ENODEV;
|
||||||
} else if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
|
} else if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
|
||||||
if (retries--) {
|
if (retries--) {
|
||||||
usleep_range(200, 300);
|
usleep_range(200, 300);
|
||||||
status = hub_port_status(hub, port1, &portstatus,
|
status = usb_hub_port_status(hub, port1, &portstatus,
|
||||||
&portchange);
|
&portchange);
|
||||||
goto retry;
|
goto retry;
|
||||||
}
|
}
|
||||||
@ -3409,7 +3410,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
|
|||||||
u16 portstatus, portchange;
|
u16 portstatus, portchange;
|
||||||
|
|
||||||
portstatus = portchange = 0;
|
portstatus = portchange = 0;
|
||||||
ret = hub_port_status(hub, port1, &portstatus,
|
ret = usb_hub_port_status(hub, port1, &portstatus,
|
||||||
&portchange);
|
&portchange);
|
||||||
|
|
||||||
dev_dbg(&port_dev->dev,
|
dev_dbg(&port_dev->dev,
|
||||||
@ -3587,13 +3588,13 @@ static int wait_for_connected(struct usb_device *udev,
|
|||||||
while (delay_ms < 2000) {
|
while (delay_ms < 2000) {
|
||||||
if (status || *portstatus & USB_PORT_STAT_CONNECTION)
|
if (status || *portstatus & USB_PORT_STAT_CONNECTION)
|
||||||
break;
|
break;
|
||||||
if (!port_is_power_on(hub, *portstatus)) {
|
if (!usb_port_is_power_on(hub, *portstatus)) {
|
||||||
status = -ENODEV;
|
status = -ENODEV;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
msleep(20);
|
msleep(20);
|
||||||
delay_ms += 20;
|
delay_ms += 20;
|
||||||
status = hub_port_status(hub, port1, portstatus, portchange);
|
status = usb_hub_port_status(hub, port1, portstatus, portchange);
|
||||||
}
|
}
|
||||||
dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms);
|
dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms);
|
||||||
return status;
|
return status;
|
||||||
@ -3653,7 +3654,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
|
|||||||
usb_lock_port(port_dev);
|
usb_lock_port(port_dev);
|
||||||
|
|
||||||
/* Skip the initial Clear-Suspend step for a remote wakeup */
|
/* Skip the initial Clear-Suspend step for a remote wakeup */
|
||||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
|
||||||
if (status == 0 && !port_is_suspended(hub, portstatus)) {
|
if (status == 0 && !port_is_suspended(hub, portstatus)) {
|
||||||
if (portchange & USB_PORT_STAT_C_SUSPEND)
|
if (portchange & USB_PORT_STAT_C_SUSPEND)
|
||||||
pm_wakeup_event(&udev->dev, 0);
|
pm_wakeup_event(&udev->dev, 0);
|
||||||
@ -3678,7 +3679,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
|
|||||||
* stop resume signaling. Then finish the resume
|
* stop resume signaling. Then finish the resume
|
||||||
* sequence.
|
* sequence.
|
||||||
*/
|
*/
|
||||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
SuspendCleared:
|
SuspendCleared:
|
||||||
@ -3791,7 +3792,7 @@ static int check_ports_changed(struct usb_hub *hub)
|
|||||||
u16 portstatus, portchange;
|
u16 portstatus, portchange;
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
status = hub_port_status(hub, port1, &portstatus, &portchange);
|
status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
|
||||||
if (!status && portchange)
|
if (!status && portchange)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -4554,7 +4555,7 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)
|
|||||||
struct usb_port *port_dev = hub->ports[port1 - 1];
|
struct usb_port *port_dev = hub->ports[port1 - 1];
|
||||||
|
|
||||||
for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
|
for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
|
||||||
ret = hub_port_status(hub, port1, &portstatus, &portchange);
|
ret = usb_hub_port_status(hub, port1, &portstatus, &portchange);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -5240,7 +5241,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
|
|||||||
* but only if the port isn't owned by someone else.
|
* but only if the port isn't owned by someone else.
|
||||||
*/
|
*/
|
||||||
if (hub_is_port_power_switchable(hub)
|
if (hub_is_port_power_switchable(hub)
|
||||||
&& !port_is_power_on(hub, portstatus)
|
&& !usb_port_is_power_on(hub, portstatus)
|
||||||
&& !port_dev->port_owner)
|
&& !port_dev->port_owner)
|
||||||
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
|
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
|
||||||
|
|
||||||
@ -5557,7 +5558,7 @@ static void port_event(struct usb_hub *hub, int port1)
|
|||||||
clear_bit(port1, hub->event_bits);
|
clear_bit(port1, hub->event_bits);
|
||||||
clear_bit(port1, hub->wakeup_bits);
|
clear_bit(port1, hub->wakeup_bits);
|
||||||
|
|
||||||
if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
|
if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
||||||
@ -5594,7 +5595,7 @@ static void port_event(struct usb_hub *hub, int port1)
|
|||||||
USB_PORT_FEAT_C_OVER_CURRENT);
|
USB_PORT_FEAT_C_OVER_CURRENT);
|
||||||
msleep(100); /* Cool down */
|
msleep(100); /* Cool down */
|
||||||
hub_power_on(hub, true);
|
hub_power_on(hub, true);
|
||||||
hub_port_status(hub, port1, &status, &unused);
|
usb_hub_port_status(hub, port1, &status, &unused);
|
||||||
if (status & USB_PORT_STAT_OVERCURRENT)
|
if (status & USB_PORT_STAT_OVERCURRENT)
|
||||||
dev_err(&port_dev->dev, "over-current condition\n");
|
dev_err(&port_dev->dev, "over-current condition\n");
|
||||||
}
|
}
|
||||||
@ -5638,7 +5639,7 @@ static void port_event(struct usb_hub *hub, int port1)
|
|||||||
u16 unused;
|
u16 unused;
|
||||||
|
|
||||||
msleep(20);
|
msleep(20);
|
||||||
hub_port_status(hub, port1, &portstatus, &unused);
|
usb_hub_port_status(hub, port1, &portstatus, &unused);
|
||||||
dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n");
|
dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n");
|
||||||
continue;
|
continue;
|
||||||
} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
|
} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
|
||||||
|
@ -121,6 +121,9 @@ extern int hub_port_debounce(struct usb_hub *hub, int port1,
|
|||||||
bool must_be_connected);
|
bool must_be_connected);
|
||||||
extern int usb_clear_port_feature(struct usb_device *hdev,
|
extern int usb_clear_port_feature(struct usb_device *hdev,
|
||||||
int port1, int feature);
|
int port1, int feature);
|
||||||
|
extern int usb_hub_port_status(struct usb_hub *hub, int port1,
|
||||||
|
u16 *status, u16 *change);
|
||||||
|
extern int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus);
|
||||||
|
|
||||||
static inline bool hub_is_port_power_switchable(struct usb_hub *hub)
|
static inline bool hub_is_port_power_switchable(struct usb_hub *hub)
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,88 @@ static int usb_port_block_power_off;
|
|||||||
|
|
||||||
static const struct attribute_group *port_dev_group[];
|
static const struct attribute_group *port_dev_group[];
|
||||||
|
|
||||||
|
static ssize_t disable_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct usb_port *port_dev = to_usb_port(dev);
|
||||||
|
struct usb_device *hdev = to_usb_device(dev->parent->parent);
|
||||||
|
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
||||||
|
struct usb_interface *intf = to_usb_interface(hub->intfdev);
|
||||||
|
int port1 = port_dev->portnum;
|
||||||
|
u16 portstatus, unused;
|
||||||
|
bool disabled;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = usb_autopm_get_interface(intf);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
usb_lock_device(hdev);
|
||||||
|
if (hub->disconnected) {
|
||||||
|
rc = -ENODEV;
|
||||||
|
goto out_hdev_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_hub_port_status(hub, port1, &portstatus, &unused);
|
||||||
|
disabled = !usb_port_is_power_on(hub, portstatus);
|
||||||
|
|
||||||
|
out_hdev_lock:
|
||||||
|
usb_unlock_device(hdev);
|
||||||
|
usb_autopm_put_interface(intf);
|
||||||
|
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%s\n", disabled ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t disable_store(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct usb_port *port_dev = to_usb_port(dev);
|
||||||
|
struct usb_device *hdev = to_usb_device(dev->parent->parent);
|
||||||
|
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
||||||
|
struct usb_interface *intf = to_usb_interface(hub->intfdev);
|
||||||
|
int port1 = port_dev->portnum;
|
||||||
|
bool disabled;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = strtobool(buf, &disabled);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
rc = usb_autopm_get_interface(intf);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
usb_lock_device(hdev);
|
||||||
|
if (hub->disconnected) {
|
||||||
|
rc = -ENODEV;
|
||||||
|
goto out_hdev_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled && port_dev->child)
|
||||||
|
usb_disconnect(&port_dev->child);
|
||||||
|
|
||||||
|
rc = usb_hub_set_port_power(hdev, hub, port1, !disabled);
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
|
||||||
|
if (!port_dev->is_superspeed)
|
||||||
|
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rc)
|
||||||
|
rc = count;
|
||||||
|
|
||||||
|
out_hdev_lock:
|
||||||
|
usb_unlock_device(hdev);
|
||||||
|
usb_autopm_put_interface(intf);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
static DEVICE_ATTR_RW(disable);
|
||||||
|
|
||||||
static ssize_t location_show(struct device *dev,
|
static ssize_t location_show(struct device *dev,
|
||||||
struct device_attribute *attr, char *buf)
|
struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
@ -153,6 +235,7 @@ static struct attribute *port_dev_attrs[] = {
|
|||||||
&dev_attr_location.attr,
|
&dev_attr_location.attr,
|
||||||
&dev_attr_quirks.attr,
|
&dev_attr_quirks.attr,
|
||||||
&dev_attr_over_current_count.attr,
|
&dev_attr_over_current_count.attr,
|
||||||
|
&dev_attr_disable.attr,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user