Commit 05f91689 authored by Lan Tianyu's avatar Lan Tianyu Committed by Greg Kroah-Hartman

usb/acpi: Store info on device removability.

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: default avatarLan Tianyu <tianyu.lan@intel.com>
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent d5575424
......@@ -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
......
......@@ -19,20 +19,29 @@
#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;
if (pld.user_visible)
usb_set_hub_port_connect_type(hdev, port1,
USB_PORT_CONNECT_TYPE_HOT_PLUG);
else
udev->removable = USB_DEVICE_FIXED;
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;
}
......
......@@ -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);
......
......@@ -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.
*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment