usb/acpi: Store info on device removability.
authorLan Tianyu <tianyu.lan@intel.com>
Wed, 5 Sep 2012 05:44:34 +0000 (13:44 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 10 Sep 2012 20:04:01 +0000 (13:04 -0700)
In the upcoming USB port power off patches, we need to know whether a
USB port can ever see a disconnect event.  Often USB ports are internal
to a system, and users can't disconnect USB devices from that port.
Sometimes those ports will remain empty, because the OEM chose not to
connect an internal USB device to that port.

According to ACPI Spec 9.13, PLD indicates whether USB port is
user visible and _UPC indicates whether a USB device can be connected to
the USB port (we'll call this "connectible").  Here's a matrix of the
possible combinations:

Visible Connectible
Name Example
-------------------------------------------------------------------------

Yes No Unknown (Invalid state.)

Yes Yes Hot-plug USB ports on the outside of a laptop.
A user could freely connect and disconnect
USB devices.

No Yes Hard-wired A USB modem hard-wired to a port on the
inside of a laptop.

No No Not used The port is internal to the system and
will remain empty.

Represent each of these four states with an enum usb_port_connect_type.
The four states are USB_PORT_CONNECT_TYPE_UNKNOWN,
USB_PORT_CONNECT_TYPE_HOT_PLUG, USB_PORT_CONNECT_TYPE_HARD_WIRED, and
USB_PORT_NOT_USED.  When we get the USB port's acpi_handle, store the
state in connect_type in struct usb_port.

Signed-off-by: Lan Tianyu <tianyu.lan@intel.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/hub.c
drivers/usb/core/usb-acpi.c
drivers/usb/core/usb.h
include/linux/usb.h

index 3341b55..4ffe837 100644 (file)
@@ -43,6 +43,7 @@ struct usb_port {
        struct usb_device *child;
        struct device dev;
        struct dev_state *port_owner;
+       enum usb_port_connect_type connect_type;
 };
 
 struct usb_hub {
@@ -5078,6 +5079,36 @@ struct usb_device *usb_hub_find_child(struct usb_device *hdev,
 }
 EXPORT_SYMBOL_GPL(usb_hub_find_child);
 
+/**
+ * usb_set_hub_port_connect_type - set hub port connect type.
+ * @hdev: USB device belonging to the usb hub
+ * @port1: port num of the port
+ * @type: connect type of the port
+ */
+void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
+       enum usb_port_connect_type type)
+{
+       struct usb_hub *hub = hdev_to_hub(hdev);
+
+       hub->ports[port1 - 1]->connect_type = type;
+}
+
+/**
+ * usb_get_hub_port_connect_type - Get the port's connect type
+ * @hdev: USB device belonging to the usb hub
+ * @port1: port num of the port
+ *
+ * Return connect type of the port and if input params are
+ * invalid, return USB_PORT_CONNECT_TYPE_UNKNOWN.
+ */
+enum usb_port_connect_type
+usb_get_hub_port_connect_type(struct usb_device *hdev, int port1)
+{
+       struct usb_hub *hub = hdev_to_hub(hdev);
+
+       return hub->ports[port1 - 1]->connect_type;
+}
+
 #ifdef CONFIG_ACPI
 /**
  * usb_get_hub_port_acpi_handle - Get the usb port's acpi handle
index 47197bf..404d86a 100644 (file)
 
 #include "usb.h"
 
-static int usb_acpi_check_upc(struct usb_device *udev, acpi_handle handle)
+static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
+       acpi_handle handle, int port1)
 {
        acpi_status status;
        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
        union acpi_object *upc;
+       struct acpi_pld pld;
        int ret = 0;
 
-       status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
-
+       /*
+        * Accoding to ACPI Spec 9.13. PLD indicates whether usb port is
+        * user visible and _UPC indicates whether it is connectable. If
+        * the port was visible and connectable, it could be freely connected
+        * and disconnected with USB devices. If no visible and connectable,
+        * a usb device is directly hard-wired to the port. If no visible and
+        * no connectable, the port would be not used.
+        */
+       status = acpi_get_physical_device_location(handle, &pld);
        if (ACPI_FAILURE(status))
                return -ENODEV;
 
+       status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
        upc = buffer.pointer;
-
        if (!upc || (upc->type != ACPI_TYPE_PACKAGE)
                || upc->package.count != 4) {
                ret = -EINVAL;
@@ -40,33 +49,20 @@ static int usb_acpi_check_upc(struct usb_device *udev, acpi_handle handle)
        }
 
        if (upc->package.elements[0].integer.value)
-               udev->removable = USB_DEVICE_REMOVABLE;
-       else
-               udev->removable = USB_DEVICE_FIXED;
+               if (pld.user_visible)
+                       usb_set_hub_port_connect_type(hdev, port1,
+                               USB_PORT_CONNECT_TYPE_HOT_PLUG);
+               else
+                       usb_set_hub_port_connect_type(hdev, port1,
+                               USB_PORT_CONNECT_TYPE_HARD_WIRED);
+       else if (!pld.user_visible)
+               usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED);
 
 out:
        kfree(upc);
        return ret;
 }
 
-static int usb_acpi_check_pld(struct usb_device *udev, acpi_handle handle)
-{
-       acpi_status status;
-       struct acpi_pld pld;
-
-       status = acpi_get_physical_device_location(handle, &pld);
-
-       if (ACPI_FAILURE(status))
-               return -ENODEV;
-
-       if (pld.user_visible)
-               udev->removable = USB_DEVICE_REMOVABLE;
-       else
-               udev->removable = USB_DEVICE_FIXED;
-
-       return 0;
-}
-
 static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
 {
        struct usb_device *udev;
@@ -88,8 +84,30 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
         */
        if (is_usb_device(dev)) {
                udev = to_usb_device(dev);
-               if (udev->parent)
+               if (udev->parent) {
+                       enum usb_port_connect_type type;
+
+                       /*
+                        * According usb port's connect type to set usb device's
+                        * removability.
+                        */
+                       type = usb_get_hub_port_connect_type(udev->parent,
+                               udev->portnum);
+                       switch (type) {
+                       case USB_PORT_CONNECT_TYPE_HOT_PLUG:
+                               udev->removable = USB_DEVICE_REMOVABLE;
+                               break;
+                       case USB_PORT_CONNECT_TYPE_HARD_WIRED:
+                               udev->removable = USB_DEVICE_FIXED;
+                               break;
+                       default:
+                               udev->removable = USB_DEVICE_REMOVABLE_UNKNOWN;
+                               break;
+                       }
+
                        return -ENODEV;
+               }
+
                /* root hub's parent is the usb hcd. */
                parent_handle = DEVICE_ACPI_HANDLE(dev->parent);
                *handle = acpi_get_child(parent_handle, udev->portnum);
@@ -122,18 +140,10 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle)
                        if (!*handle)
                                return -ENODEV;
                }
+               usb_acpi_check_port_connect_type(udev, *handle, port_num);
        } else
                return -ENODEV;
 
-       /*
-        * PLD will tell us whether a port is removable to the user or
-        * not. If we don't get an answer from PLD (it's not present
-        * or it's malformed) then try to infer it from UPC. If a
-        * device isn't connectable then it's probably not removable.
-        */
-       if (usb_acpi_check_pld(udev, *handle) != 0)
-               usb_acpi_check_upc(udev, *handle);
-
        return 0;
 }
 
index 1633f6e..1c528c1 100644 (file)
@@ -169,6 +169,10 @@ extern void usb_notify_add_device(struct usb_device *udev);
 extern void usb_notify_remove_device(struct usb_device *udev);
 extern void usb_notify_add_bus(struct usb_bus *ubus);
 extern void usb_notify_remove_bus(struct usb_bus *ubus);
+extern enum usb_port_connect_type
+       usb_get_hub_port_connect_type(struct usb_device *hdev, int port1);
+extern void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
+       enum usb_port_connect_type type);
 
 #ifdef CONFIG_ACPI
 extern int usb_acpi_register(void);
index ff8ef2d..e0084a1 100644 (file)
@@ -384,6 +384,13 @@ enum usb_device_removable {
        USB_DEVICE_FIXED,
 };
 
+enum usb_port_connect_type {
+       USB_PORT_CONNECT_TYPE_UNKNOWN = 0,
+       USB_PORT_CONNECT_TYPE_HOT_PLUG,
+       USB_PORT_CONNECT_TYPE_HARD_WIRED,
+       USB_PORT_NOT_USED,
+};
+
 /*
  * USB 3.0 Link Power Management (LPM) parameters.
  *