|
@@ -60,6 +60,8 @@ struct usb_hub {
|
|
|
status change */
|
|
|
unsigned long busy_bits[1]; /* ports being reset or
|
|
|
resumed */
|
|
|
+ unsigned long removed_bits[1]; /* ports with a "removed"
|
|
|
+ device present */
|
|
|
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
|
|
|
#error event_bits[] is too short!
|
|
|
#endif
|
|
@@ -635,6 +637,33 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
|
|
|
kick_khubd(hub);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * usb_remove_device - disable a device's port on its parent hub
|
|
|
+ * @udev: device to be disabled and removed
|
|
|
+ * Context: @udev locked, must be able to sleep.
|
|
|
+ *
|
|
|
+ * After @udev's port has been disabled, khubd is notified and it will
|
|
|
+ * see that the device has been disconnected. When the device is
|
|
|
+ * physically unplugged and something is plugged in, the events will
|
|
|
+ * be received and processed normally.
|
|
|
+ */
|
|
|
+int usb_remove_device(struct usb_device *udev)
|
|
|
+{
|
|
|
+ struct usb_hub *hub;
|
|
|
+ struct usb_interface *intf;
|
|
|
+
|
|
|
+ if (!udev->parent) /* Can't remove a root hub */
|
|
|
+ return -EINVAL;
|
|
|
+ hub = hdev_to_hub(udev->parent);
|
|
|
+ intf = to_usb_interface(hub->intfdev);
|
|
|
+
|
|
|
+ usb_autopm_get_interface(intf);
|
|
|
+ set_bit(udev->portnum, hub->removed_bits);
|
|
|
+ hub_port_logical_disconnect(hub, udev->portnum);
|
|
|
+ usb_autopm_put_interface(intf);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
enum hub_activation_type {
|
|
|
HUB_INIT, HUB_INIT2, HUB_INIT3,
|
|
|
HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME,
|
|
@@ -730,6 +759,13 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
|
|
|
USB_PORT_FEAT_C_ENABLE);
|
|
|
}
|
|
|
|
|
|
+ /* We can forget about a "removed" device when there's a
|
|
|
+ * physical disconnect or the connect status changes.
|
|
|
+ */
|
|
|
+ if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
+ (portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
+ clear_bit(port1, hub->removed_bits);
|
|
|
+
|
|
|
if (!udev || udev->state == USB_STATE_NOTATTACHED) {
|
|
|
/* Tell khubd to disconnect the device or
|
|
|
* check for a new connection
|
|
@@ -2965,6 +3001,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
|
|
usb_disconnect(&hdev->children[port1-1]);
|
|
|
clear_bit(port1, hub->change_bits);
|
|
|
|
|
|
+ /* We can forget about a "removed" device when there's a physical
|
|
|
+ * disconnect or the connect status changes.
|
|
|
+ */
|
|
|
+ if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
+ (portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
+ clear_bit(port1, hub->removed_bits);
|
|
|
+
|
|
|
if (portchange & (USB_PORT_STAT_C_CONNECTION |
|
|
|
USB_PORT_STAT_C_ENABLE)) {
|
|
|
status = hub_port_debounce(hub, port1);
|
|
@@ -2978,8 +3021,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /* Return now if debouncing failed or nothing is connected */
|
|
|
- if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
|
|
|
+ /* Return now if debouncing failed or nothing is connected or
|
|
|
+ * the device was "removed".
|
|
|
+ */
|
|
|
+ if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
+ test_bit(port1, hub->removed_bits)) {
|
|
|
|
|
|
/* maybe switch power back on (e.g. root hub was reset) */
|
|
|
if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
|