Platform firmware identifies ports that share a connector. This information is relevant for port power control since we do not want a device to reconnect on its peer port in a connector when pm has decided to power-off its currently connected port. In the case of xhci, peer ports are attached through separate root hubs (and zero or more integrated hubs). 'struct usb_domain' is introduced so that each root hub 'hcd' can register its ports to a unified 'platform' domain where connector relationships can be evaluated. A connector is formed when two ports are registered with identical 'match data'. ACPI defines connectors by specifying a common 'group token' and 'group position' property for the port [1]. Outside of this match data there is nothing firmware specific about this implementation, hence the creation of the common rountines in usb-platform.c. [1] ACPI 5.0 Section 6.1.8 _PLD (Physical Device Location) Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/Kconfig | 4 + drivers/usb/core/Makefile | 1 drivers/usb/core/hcd-pci.c | 17 +++- drivers/usb/core/hcd.c | 2 drivers/usb/core/hub.h | 8 ++ drivers/usb/core/usb-acpi.c | 69 +++++++++++++++-- drivers/usb/core/usb-platform.c | 162 +++++++++++++++++++++++++++++++++++++++ drivers/usb/core/usb-platform.h | 44 +++++++++++ drivers/usb/host/xhci-pci.c | 16 +++- include/linux/usb/hcd.h | 9 ++ 10 files changed, 320 insertions(+), 12 deletions(-) create mode 100644 drivers/usb/core/usb-platform.c create mode 100644 drivers/usb/core/usb-platform.h diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index db535b0aa172..e439a14b0210 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -89,3 +89,7 @@ config USB_OTG_BLACKLIST_HUB and software costs by not supporting external hubs. So are "Embedded Hosts" that don't offer OTG support. +config USB_PLATFORM + bool + default y if ACPI + diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index 5e847ad2f58a..b534b9b28c69 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -11,5 +11,6 @@ usbcore-y += port.o usbcore-$(CONFIG_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o +usbcore-$(CONFIG_USB_PLATFORM) += usb-platform.o obj-$(CONFIG_USB) += usbcore.o diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index dfe9d0f22978..542dc0f6ef74 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -161,20 +161,23 @@ static void ehci_wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd, /* always called with process context; sleeping is OK */ /** - * usb_hcd_pci_probe - initialize PCI-based HCDs + * usb_hcd_pci_probe_domain - initialize PCI-based HCDs * @dev: USB Host Controller being probed * @id: pci hotplug id connecting controller to HCD framework + * @domain: if this hcd coordinates port activity with another hcd from pdev * Context: !in_interrupt() * * Allocates basic PCI resources for this USB host controller, and * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. * - * Store this function in the HCD's struct pci_driver as probe(). + * Note the usb_domain is not to be confused with companion controllers which + * multiplex the same phy with multiple controllers. * * Return: 0 if successful. */ -int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +int usb_hcd_pci_probe_domain(struct pci_dev *dev, struct usb_domain *udom, + const struct pci_device_id *id) { struct hc_driver *driver; struct usb_hcd *hcd; @@ -215,6 +218,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) goto disable_pci; } + hcd->domain = usb_get_domain(udom); hcd->amd_resume_bug = (usb_hcd_amd_remote_wakeup_quirk(dev) && driver->flags & (HCD_USB11 | HCD_USB3)) ? 1 : 0; @@ -301,6 +305,13 @@ disable_pci: dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval); return retval; } +EXPORT_SYMBOL_GPL(usb_hcd_pci_probe_domain); + + +int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return usb_hcd_pci_probe_domain(dev, NULL, id); +} EXPORT_SYMBOL_GPL(usb_hcd_pci_probe); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 6bffb8c87bc9..892574f439a4 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2505,6 +2505,8 @@ static void hcd_release (struct kref *kref) kfree(hcd->bandwidth_mutex); else hcd->shared_hcd->shared_hcd = NULL; + + usb_put_domain(hcd->domain); kfree(hcd); } diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 4e4790dea343..9ea075d1b7a3 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -19,6 +19,9 @@ * for more details. */ +#ifndef __USB_HUB_H__ +#define __USB_HUB_H__ + #include <linux/usb.h> #include <linux/usb/ch11.h> #include <linux/usb/hcd.h> @@ -80,6 +83,8 @@ struct usb_hub { * struct usb port - kernel's representation of a usb port * @child: usb device attatched to the port * @dev: generic device interface + * @node: peer ports in a given connector + * @connector: parent connector for this port * @port_owner: port's owner * @connect_type: port's connect type * @portnum: port index num based one @@ -88,6 +93,8 @@ struct usb_hub { */ struct usb_port { struct usb_device *child; + struct list_head node; + struct usb_connector *connector; struct device dev; struct dev_state *port_owner; enum usb_port_connect_type connect_type; @@ -123,3 +130,4 @@ static inline int hub_port_debounce_be_stable(struct usb_hub *hub, return hub_port_debounce(hub, port1, false); } +#endif /* __USB_HUB_H__ */ diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 255c14464bf2..fab4a8288803 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -18,7 +18,8 @@ #include <linux/usb/hcd.h> #include <acpi/acpi_bus.h> -#include "usb.h" +#include "hub.h" +#include "usb-platform.h" /** * usb_acpi_power_manageable - check whether usb port has @@ -42,6 +43,51 @@ bool usb_acpi_power_manageable(struct usb_device *hdev, int index) } EXPORT_SYMBOL_GPL(usb_acpi_power_manageable); +struct usb_acpi_pair_data { + u8 group_token; + u8 group_position; +}; + +static void usb_acpi_pair_port(struct usb_device *hdev, acpi_handle *handle, + int port1, struct acpi_pld_info *pld) +{ + struct usb_acpi_pair_data data; + struct usb_port *port_dev; + struct usb_hcd *hcd; + struct usb_hub *hub; + + if (!pld) + return; + + hub = usb_hub_to_struct_hub(hdev); + if (!hub) + return; + + port_dev = hub->ports[port1 - 1]; + hcd = bus_to_hcd(hdev->bus); + + memset(&data, 0, sizeof(data)); + data.group_token = pld->group_token; + data.group_position = pld->group_position; + usb_domain_pair_port(hcd->domain, port_dev, &data, sizeof(data)); +} + +static void usb_acpi_cleanup(struct device *dev) +{ + struct usb_port *port_dev; + struct usb_device *hdev; + struct usb_hcd *hcd; + + if (!is_usb_port(dev)) + return; + + hdev = to_usb_device(dev->parent->parent); + port_dev = to_usb_port(dev); + hcd = bus_to_hcd(hdev->bus); + + usb_domain_remove_port(hcd->domain, port_dev); +} + /** * usb_acpi_set_power_state - control usb port's power via acpi power * resource @@ -83,12 +129,11 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable) EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); static int usb_acpi_check_port_connect_type(struct usb_device *hdev, - acpi_handle handle, int port1) + acpi_handle handle, int port1, struct acpi_pld_info *pld) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *upc; - struct acpi_pld_info *pld; int ret = 0; /* @@ -99,8 +144,7 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev, * 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)) + if (!pld) return -ENODEV; status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); @@ -122,7 +166,6 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev, usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED); out: - ACPI_FREE(pld); kfree(upc); return ret; } @@ -179,6 +222,9 @@ static int usb_acpi_find_device(struct device *dev, acpi_handle *handle) return -ENODEV; return 0; } else if (is_usb_port(dev)) { + struct acpi_pld_info *pld; + acpi_status status; + sscanf(dev_name(dev), "port%d", &port_num); /* Get the struct usb_device point of port's hub */ udev = to_usb_device(dev->parent->parent); @@ -209,7 +255,15 @@ 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); + + status = acpi_get_physical_device_location(*handle, &pld); + if (ACPI_FAILURE(status)) + pld = NULL; + + usb_acpi_check_port_connect_type(udev, *handle, port_num, pld); + usb_acpi_pair_port(udev, *handle, port_num, pld); + + ACPI_FREE(pld); } else return -ENODEV; @@ -225,6 +279,7 @@ static struct acpi_bus_type usb_acpi_bus = { .name = "USB", .match = usb_acpi_bus_match, .find_device = usb_acpi_find_device, + .cleanup = usb_acpi_cleanup, }; int usb_acpi_register(void) diff --git a/drivers/usb/core/usb-platform.c b/drivers/usb/core/usb-platform.c new file mode 100644 index 000000000000..476ac3fa47c2 --- /dev/null +++ b/drivers/usb/core/usb-platform.c @@ -0,0 +1,162 @@ +/* + * USB support for platform defined connectors / port-pairings + * + * Copyright 2013 Intel Corporation <dan.j.williams@xxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 2. + * + */ +#include <linux/device.h> +#include "usb-platform.h" + +static void domain_release(struct kref *kref) +{ + struct usb_domain *udom = container_of(kref, struct usb_domain, kref); + + WARN(!list_empty(&udom->connectors), "connectors list not empty\n"); + + kfree(udom); +} + +void usb_put_domain(struct usb_domain *udom) +{ + if (udom) + kref_put(&udom->kref, domain_release); +} +EXPORT_SYMBOL_GPL(usb_put_domain); + +struct usb_domain *usb_get_domain(struct usb_domain *udom) +{ + if (udom) + kref_get(&udom->kref); + return udom; +} +EXPORT_SYMBOL_GPL(usb_get_domain); + +struct usb_domain *usb_create_domain(void) +{ + struct usb_domain *udom = kmalloc(sizeof(*udom), GFP_KERNEL); + + if (!udom) + return NULL; + + INIT_LIST_HEAD(&udom->connectors); + mutex_init(&udom->lock); + kref_init(&udom->kref); + return udom; +} +EXPORT_SYMBOL_GPL(usb_create_domain); + +static struct usb_connector *create_connector(struct usb_domain *udom, + struct usb_port *port_dev, + size_t pair_data) +{ + struct usb_connector *uconn; + + uconn = kzalloc(sizeof(*uconn) + pair_data, GFP_KERNEL); + if (!uconn) + return NULL; + + uconn->connect_type = port_dev->connect_type; + INIT_LIST_HEAD(&uconn->ports); + INIT_LIST_HEAD(&uconn->node); + uconn->domain = udom; + + return uconn; +} + +static void check_connector(struct usb_connector *uconn, + struct usb_port *port_dev) +{ + if (uconn->connect_type != port_dev->connect_type) { + struct device *intf_dev = port_dev->dev.parent; + + dev_info(intf_dev, "port%d inconsistent connect types %d:%d", + port_dev->portnum, port_dev->connect_type, + uconn->connect_type); + list_for_each_entry(port_dev, &uconn->ports, node) + port_dev->connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; + uconn->connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; + } +} + +static void add_port_connector(struct usb_port *port_dev, + struct usb_connector *uconn) +{ + list_add(&port_dev->node, &uconn->ports); + port_dev->connector = uconn; + check_connector(uconn, port_dev); +} + +static void remove_port_connector(struct usb_port *port_dev) +{ + list_del_init(&port_dev->node); + port_dev->connector = NULL; +} + +int usb_domain_pair_port(struct usb_domain *udom, struct usb_port *port_dev, + void *data, size_t size) +{ + struct usb_connector *uconn; + int rc = -ENODEV; + + if (!udom) + return 0; + + mutex_lock(&udom->lock); + list_for_each_entry(uconn, &udom->connectors, node) { + if (memcmp(data, uconn->pair_data, size) != 0) + continue; + add_port_connector(port_dev, uconn); + port_dev = NULL; + break; + } + while (port_dev) { + uconn = create_connector(udom, port_dev, size); + if (!uconn) { + rc = -ENOMEM; + break; + } + usb_get_domain(udom); + memcpy(uconn->pair_data, data, size); + list_add(&uconn->node, &udom->connectors); + add_port_connector(port_dev, uconn); + break; + } + mutex_unlock(&udom->lock); + + return rc; +} + +void usb_domain_remove_port(struct usb_domain *udom, struct usb_port *port_dev) +{ + struct usb_connector *uconn; + + if (!udom) + return; + + usb_get_domain(udom); + mutex_lock(&udom->lock); + list_for_each_entry(uconn, &udom->connectors, node) { + struct usb_port *p; + + list_for_each_entry(p, &uconn->ports, node) + if (p == port_dev) { + remove_port_connector(port_dev); + break; + } + + if (!list_empty(&uconn->ports)) + continue; + + /* connector is empty, drop it from the domain */ + list_del(&uconn->node); + usb_put_domain(udom); + kfree(uconn); + break; + } + mutex_unlock(&udom->lock); + usb_put_domain(udom); +} diff --git a/drivers/usb/core/usb-platform.h b/drivers/usb/core/usb-platform.h new file mode 100644 index 000000000000..768bc1d4c831 --- /dev/null +++ b/drivers/usb/core/usb-platform.h @@ -0,0 +1,44 @@ +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/kref.h> +#include <linux/usb.h> + +#include "hub.h" + +/** + * struct usb_domain - track pair ports by connector in this domain + * @connectors: list of struct usb_connector instances + * @lock: lock manipulation of 'connectors' + * @kref: ref count domain users + * + * Platform data describes the topology of the domain. For example, + * ACPI may specify that port1 on hcd1 is a peer with port1 on hcd2 + * where hcd1,2 share a common controller (as is the case with XHCI). + * Firmware stashes port to 'connector' relationships in this structure + * when it binds to the port devices. (See: struct usb_connector) + */ +struct usb_domain { + struct list_head connectors; + struct mutex lock; + struct kref kref; +}; + +/** + * struct usb_connector - platform defined container of one or more ports + * @ports: platform defined ports that share this same connector + * @node: sibling connectors in the struct usb_domain + * @connect_type: unified connection type defined by the platform + * @domain: parent domain for these connectors + * @pair_data: opaque buffer for storing platform-specific port-pairing data + */ +struct usb_connector { + struct list_head ports; + struct list_head node; + enum usb_port_connect_type connect_type; + struct usb_domain *domain; + u8 pair_data[0]; +}; + +int usb_domain_pair_port(struct usb_domain *udom, struct usb_port *port_dev, + void *data, size_t size); +void usb_domain_remove_port(struct usb_domain *udom, struct usb_port *port_dev); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index b8dffd59eb25..eb8991ec1316 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -178,6 +178,11 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) struct xhci_hcd *xhci; struct hc_driver *driver; struct usb_hcd *hcd; + struct usb_domain *udom; + + udom = usb_create_domain(); + if (!udom) + return -ENOMEM; driver = (struct hc_driver *)id->driver_data; /* Register the USB 2.0 roothub. @@ -186,10 +191,10 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) * to say USB 2.0, but I'm not sure what the implications would be in * the other parts of the HCD code. */ - retval = usb_hcd_pci_probe(dev, id); + retval = usb_hcd_pci_probe_domain(dev, udom, id); if (retval) - return retval; + goto put_domain; /* USB 2.0 roothub is stored in the PCI device now. */ hcd = dev_get_drvdata(&dev->dev); @@ -200,6 +205,7 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) retval = -ENOMEM; goto dealloc_usb2_hcd; } + xhci->shared_hcd->domain = usb_get_domain(udom); /* Set the xHCI pointer before xhci_pci_setup() (aka hcd_driver.reset) * is called by usb_add_hcd(). @@ -218,12 +224,18 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) if (xhci->quirks & XHCI_LPM_SUPPORT) hcd_to_bus(xhci->shared_hcd)->root_hub->lpm_capable = 1; + /* now only the hcd(s) pin the domain */ + usb_put_domain(udom); + return 0; put_usb3_hcd: usb_put_hcd(xhci->shared_hcd); dealloc_usb2_hcd: usb_hcd_pci_remove(dev); +put_domain: + usb_put_domain(udom); + return retval; } diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index b8aba196f7f1..33e45e9ec81b 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -76,6 +76,7 @@ struct giveback_urb_bh { struct usb_host_endpoint *completing_ep; }; +struct usb_domain; struct usb_hcd { /* @@ -83,6 +84,7 @@ struct usb_hcd { */ struct usb_bus self; /* hcd is-a bus */ struct kref kref; /* reference counter */ + struct usb_domain *domain; /* platform port assocs */ const char *product_desc; /* product/vendor string */ int speed; /* Speed for this roothub. @@ -412,6 +414,10 @@ extern int usb_hcd_alloc_bandwidth(struct usb_device *udev, struct usb_host_interface *new_alt); extern int usb_hcd_get_frame_number(struct usb_device *udev); +extern struct usb_domain *usb_create_domain(void); +extern struct usb_domain *usb_get_domain(struct usb_domain *udom); +extern void usb_put_domain(struct usb_domain *udom); + extern struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, struct device *dev, const char *bus_name); extern struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, @@ -433,6 +439,9 @@ struct pci_dev; struct pci_device_id; extern int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id); +extern int usb_hcd_pci_probe_domain(struct pci_dev *dev, + struct usb_domain *udom, + const struct pci_device_id *id); extern void usb_hcd_pci_remove(struct pci_dev *dev); extern void usb_hcd_pci_shutdown(struct pci_dev *dev); -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html