On Thu, Oct 24, 2013 at 12:35:35AM -0700, Dan Williams wrote: > 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. I think I know what you mean by the above paragraph, but I'm not quite sure. Do you mean that if you power off a SuperSpeed port, the USB 3.0 device will attempt to reconnect to its shared USB 2.0 port? Or do you mean something different? Sarah Sharp > 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