From 70f400d4d957c2453c8689552ff212bc59f88938 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Mon, 24 May 2021 10:18:11 -0700 Subject: driver core: Move the "removable" attribute from USB to core Move the "removable" attribute from USB to core in order to allow it to be supported by other subsystem / buses. Individual buses that want to support this attribute can populate the removable property of the device while enumerating it with the 3 possible values - - "unknown" - "fixed" - "removable" Leaving the field unchanged (i.e. "not supported") would mean that the attribute would not show up in sysfs for that device. The UAPI (location, symantics etc) for the attribute remains unchanged. Move the "removable" attribute from USB to the device core so it can be used by other subsystems / buses. By default, devices do not have a "removable" attribute in sysfs. If a subsystem or bus driver wants to support a "removable" attribute, it should call device_set_removable() before calling device_register() or device_add(), e.g.: device_set_removable(dev, DEVICE_REMOVABLE); device_register(dev); The possible values and the resulting sysfs attribute contents are: DEVICE_REMOVABLE_UNKNOWN -> "unknown" DEVICE_REMOVABLE -> "removable" DEVICE_FIXED -> "fixed" Convert the USB "removable" attribute to use this new device core functionality. There should be no user-visible change in the location or semantics of attribute for USB devices. Reviewed-by: Bjorn Helgaas Signed-off-by: Rajat Jain Link: https://lore.kernel.org/r/20210524171812.18095-1-rajatja@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 11 ----------- Documentation/ABI/testing/sysfs-devices-removable | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-devices-removable (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index bf2c1968525f..73eb23bc1f34 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -154,17 +154,6 @@ Description: files hold a string value (enable or disable) indicating whether or not USB3 hardware LPM U1 or U2 is enabled for the device. -What: /sys/bus/usb/devices/.../removable -Date: February 2012 -Contact: Matthew Garrett -Description: - Some information about whether a given USB device is - physically fixed to the platform can be inferred from a - combination of hub descriptor bits and platform-specific data - such as ACPI. This file will read either "removable" or - "fixed" if the information is available, and "unknown" - otherwise. - What: /sys/bus/usb/devices/.../ltm_capable Date: July 2012 Contact: Sarah Sharp diff --git a/Documentation/ABI/testing/sysfs-devices-removable b/Documentation/ABI/testing/sysfs-devices-removable new file mode 100644 index 000000000000..acf7766e800b --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-removable @@ -0,0 +1,17 @@ +What: /sys/devices/.../removable +Date: May 2021 +Contact: Rajat Jain +Description: + Information about whether a given device can be removed from the + platform by the user. This is determined by its subsystem in a + bus / platform-specific way. This attribute is only present for + devices that can support determining such information: + + "removable": device can be removed from the platform by the user + "fixed": device is fixed to the platform / cannot be removed + by the user. + "unknown": The information is unavailable / cannot be deduced. + + Currently this is only supported by USB (which infers the + information from a combination of hub descriptor bits and + platform-specific data such as ACPI). -- cgit v1.2.3 From c037b6c818c30b6afa11dc70018fc4a075f26028 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Mon, 24 May 2021 10:18:12 -0700 Subject: PCI: Add sysfs "removable" attribute A PCI device is "external_facing" if it's a Root Port with the ACPI "ExternalFacingPort" property or if it has the DT "external-facing" property. We consider everything downstream from such a device to be removable by user. We're mainly concerned with consumer platforms with user accessible Thunderbolt ports that are vulnerable to DMA attacks, and we expect those ports to be identified by firmware as "ExternalFacingPort". Devices in traditional hotplug slots can technically be removed, but the expectation is that unless the port is marked with "ExternalFacingPort", such devices are less accessible to user / may not be removed by end user, and thus not exposed as "removable" to userspace. This can be used to implement userspace policies tailored for user removable devices. Eg usage: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2591812 https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2795038 (code uses such an attribute to remove external PCI devices or disable features on them as needed by the policy desired) Acked-by: Bjorn Helgaas Signed-off-by: Rajat Jain Link: https://lore.kernel.org/r/20210524171812.18095-2-rajatja@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-devices-removable | 3 ++- drivers/pci/probe.c | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-devices-removable b/Documentation/ABI/testing/sysfs-devices-removable index acf7766e800b..bda6c320c8d3 100644 --- a/Documentation/ABI/testing/sysfs-devices-removable +++ b/Documentation/ABI/testing/sysfs-devices-removable @@ -14,4 +14,5 @@ Description: Currently this is only supported by USB (which infers the information from a combination of hub descriptor bits and - platform-specific data such as ACPI). + platform-specific data such as ACPI) and PCI (which gets this + from ACPI / device tree). diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 3a62d09b8869..812e0d7fd7a7 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1575,6 +1575,26 @@ static void set_pcie_untrusted(struct pci_dev *dev) dev->untrusted = true; } +static void pci_set_removable(struct pci_dev *dev) +{ + struct pci_dev *parent = pci_upstream_bridge(dev); + + /* + * We (only) consider everything downstream from an external_facing + * device to be removable by the user. We're mainly concerned with + * consumer platforms with user accessible thunderbolt ports that are + * vulnerable to DMA attacks, and we expect those ports to be marked by + * the firmware as external_facing. Devices in traditional hotplug + * slots can technically be removed, but the expectation is that unless + * the port is marked with external_facing, such devices are less + * accessible to user / may not be removed by end user, and thus not + * exposed as "removable" to userspace. + */ + if (parent && + (parent->external_facing || dev_is_removable(&parent->dev))) + dev_set_removable(&dev->dev, DEVICE_REMOVABLE); +} + /** * pci_ext_cfg_is_aliased - Is ext config space just an alias of std config? * @dev: PCI device @@ -1822,6 +1842,8 @@ int pci_setup_device(struct pci_dev *dev) /* Early fixups, before probing the BARs */ pci_fixup_device(pci_fixup_early, dev); + pci_set_removable(dev); + pci_info(dev, "[%04x:%04x] type %02x class %#08x\n", dev->vendor, dev->device, dev->hdr_type, dev->class); -- cgit v1.2.3 From 68977e61ab9e3fbb8ebbb1c7e8c772762d232f7c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 12 Apr 2021 14:12:47 +0300 Subject: Documentation / thunderbolt: Clean up entries Align the "What" entries along with the rest and move nvm_authenticate_on_disconnect to correct place. Signed-off-by: Mika Westerberg --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 44 ++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index c41c68f64693..05afeee05538 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -1,4 +1,4 @@ -What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl +What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl Date: Jun 2018 KernelVersion: 4.17 Contact: thunderbolt-software@lists.01.org @@ -21,7 +21,7 @@ Description: Holds a comma separated list of device unique_ids that If a device is authorized automatically during boot its boot attribute is set to 1. -What: /sys/bus/thunderbolt/devices/.../domainX/deauthorization +What: /sys/bus/thunderbolt/devices/.../domainX/deauthorization Date: May 2021 KernelVersion: 5.12 Contact: Mika Westerberg @@ -30,7 +30,7 @@ Description: This attribute tells whether the system supports de-authorize PCIe tunnel by writing 0 to authorized attribute under each device. -What: /sys/bus/thunderbolt/devices/.../domainX/iommu_dma_protection +What: /sys/bus/thunderbolt/devices/.../domainX/iommu_dma_protection Date: Mar 2019 KernelVersion: 4.21 Contact: thunderbolt-software@lists.01.org @@ -39,7 +39,7 @@ Description: This attribute tells whether the system uses IOMMU it is not (DMA protection is solely based on Thunderbolt security levels). -What: /sys/bus/thunderbolt/devices/.../domainX/security +What: /sys/bus/thunderbolt/devices/.../domainX/security Date: Sep 2017 KernelVersion: 4.13 Contact: thunderbolt-software@lists.01.org @@ -61,7 +61,7 @@ Description: This attribute holds current Thunderbolt security level the BIOS. ======= ================================================== -What: /sys/bus/thunderbolt/devices/.../authorized +What: /sys/bus/thunderbolt/devices/.../authorized Date: Sep 2017 KernelVersion: 4.13 Contact: thunderbolt-software@lists.01.org @@ -95,14 +95,14 @@ Description: This attribute is used to authorize Thunderbolt devices EKEYREJECTED if the challenge response did not match. == ======================================================== -What: /sys/bus/thunderbolt/devices/.../boot +What: /sys/bus/thunderbolt/devices/.../boot Date: Jun 2018 KernelVersion: 4.17 Contact: thunderbolt-software@lists.01.org Description: This attribute contains 1 if Thunderbolt device was already authorized on boot and 0 otherwise. -What: /sys/bus/thunderbolt/devices/.../generation +What: /sys/bus/thunderbolt/devices/.../generation Date: Jan 2020 KernelVersion: 5.5 Contact: Christian Kellner @@ -110,7 +110,7 @@ Description: This attribute contains the generation of the Thunderbolt controller associated with the device. It will contain 4 for USB4. -What: /sys/bus/thunderbolt/devices/.../key +What: /sys/bus/thunderbolt/devices/.../key Date: Sep 2017 KernelVersion: 4.13 Contact: thunderbolt-software@lists.01.org @@ -226,6 +226,20 @@ Description: When new NVM image is written to the non-active NVM based mailbox before the device is power cycled. Writing 0 here clears the status. +What: /sys/bus/thunderbolt/devices/.../nvm_authenticate_on_disconnect +Date: Oct 2020 +KernelVersion: v5.9 +Contact: Mario Limonciello +Description: For supported devices, automatically authenticate the new Thunderbolt + image when the device is disconnected from the host system. + + This file will accept writing values "1" or "2" + + - Writing "1" will flush the image to the storage + area and prepare the device for authentication on disconnect. + - Writing "2" will run some basic validation on the image + and flush it to the storage area. + What: /sys/bus/thunderbolt/devices/./key Date: Jan 2018 KernelVersion: 4.15 @@ -308,17 +322,3 @@ Date: Oct 2020 KernelVersion: v5.9 Contact: Mika Westerberg Description: Retimer vendor identifier read from the hardware. - -What: /sys/bus/thunderbolt/devices/.../nvm_authenticate_on_disconnect -Date: Oct 2020 -KernelVersion: v5.9 -Contact: Mario Limonciello -Description: For supported devices, automatically authenticate the new Thunderbolt - image when the device is disconnected from the host system. - - This file will accept writing values "1" or "2" - - - Writing "1" will flush the image to the storage - area and prepare the device for authentication on disconnect. - - Writing "2" will run some basic validation on the image - and flush it to the storage area. -- cgit v1.2.3 From cae5f5151d76635f6b5c08133184c48048346e63 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 1 Apr 2021 17:34:20 +0300 Subject: thunderbolt: Add USB4 port devices Create devices for each USB4 port. This is needed when we add retimer access when there is no device connected but may be useful for other purposes too following what USB subsystem does. This exports a single attribute "link" that shows the type of the USB4 link (or "none" if there is no cable connected). Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 7 ++ drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/retimer.c | 15 ++-- drivers/thunderbolt/switch.c | 17 +++- drivers/thunderbolt/tb.h | 30 +++++++ drivers/thunderbolt/usb4.c | 54 ++++++++++++ drivers/thunderbolt/usb4_port.c | 112 ++++++++++++++++++++++++ 7 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 drivers/thunderbolt/usb4_port.c (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 05afeee05538..3537ba1ba892 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -290,6 +290,13 @@ Contact: thunderbolt-software@lists.01.org Description: This contains XDomain service specific settings as bitmask. Format: %x +What: /sys/bus/thunderbolt/devices/usb4_portX/link +Date: Sep 2021 +KernelVersion: v5.14 +Contact: Mika Westerberg +Description: Returns the current link mode. Possible values are + "usb4", "tbt" and "none". + What: /sys/bus/thunderbolt/devices/:./device Date: Oct 2020 KernelVersion: v5.9 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 7aa48f6c41d9..da19d7987d00 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -2,7 +2,7 @@ obj-${CONFIG_USB4} := thunderbolt.o thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o -thunderbolt-objs += nvm.o retimer.o quirks.o +thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o thunderbolt-${CONFIG_ACPI} += acpi.o thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index c44fad2b9fbb..2e5188fb1150 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -283,11 +283,13 @@ struct device_type tb_retimer_type = { static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status) { + struct usb4_port *usb4; struct tb_retimer *rt; u32 vendor, device; int ret; - if (!port->cap_usb4) + usb4 = port->usb4; + if (!usb4) return -EINVAL; ret = usb4_port_retimer_read(port, index, USB4_SB_VENDOR_ID, &vendor, @@ -331,7 +333,7 @@ static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status) rt->port = port; rt->tb = port->sw->tb; - rt->dev.parent = &port->sw->dev; + rt->dev.parent = &usb4->dev; rt->dev.bus = &tb_bus_type; rt->dev.type = &tb_retimer_type; dev_set_name(&rt->dev, "%s:%u.%u", dev_name(&port->sw->dev), @@ -389,7 +391,7 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index) struct tb_retimer_lookup lookup = { .port = port, .index = index }; struct device *dev; - dev = device_find_child(&port->sw->dev, &lookup, retimer_match); + dev = device_find_child(&port->usb4->dev, &lookup, retimer_match); if (dev) return tb_to_retimer(dev); @@ -479,7 +481,10 @@ static int remove_retimer(struct device *dev, void *data) */ void tb_retimer_remove_all(struct tb_port *port) { - if (port->cap_usb4) - device_for_each_child_reverse(&port->sw->dev, port, + struct usb4_port *usb4; + + usb4 = port->usb4; + if (usb4) + device_for_each_child_reverse(&usb4->dev, port, remove_retimer); } diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e015dc93a916..7303c61a891a 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2741,11 +2741,16 @@ int tb_switch_add(struct tb_switch *sw) sw->device_name); } + ret = usb4_switch_add_ports(sw); + if (ret) { + dev_err(&sw->dev, "failed to add USB4 ports\n"); + goto err_del; + } + ret = tb_switch_nvm_add(sw); if (ret) { dev_err(&sw->dev, "failed to add NVM devices\n"); - device_del(&sw->dev); - return ret; + goto err_ports; } /* @@ -2766,6 +2771,13 @@ int tb_switch_add(struct tb_switch *sw) tb_switch_debugfs_init(sw); return 0; + +err_ports: + usb4_switch_remove_ports(sw); +err_del: + device_del(&sw->dev); + + return ret; } /** @@ -2805,6 +2817,7 @@ void tb_switch_remove(struct tb_switch *sw) tb_plug_events_active(sw, false); tb_switch_nvm_remove(sw); + usb4_switch_remove_ports(sw); if (tb_route(sw)) dev_info(&sw->dev, "device disconnected\n"); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 89e38aeea52b..948e7601428f 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -202,6 +202,7 @@ struct tb_switch { * @cap_tmu: Offset of the adapter specific TMU capability (%0 if not present) * @cap_adap: Offset of the adapter specific capability (%0 if not present) * @cap_usb4: Offset to the USB4 port capability (%0 if not present) + * @usb4: Pointer to the USB4 port structure (only if @cap_usb4 is != %0) * @port: Port number on switch * @disabled: Disabled by eeprom or enabled but not implemented * @bonded: true if the port is bonded (two lanes combined as one) @@ -228,6 +229,7 @@ struct tb_port { int cap_tmu; int cap_adap; int cap_usb4; + struct usb4_port *usb4; u8 port; bool disabled; bool bonded; @@ -241,6 +243,16 @@ struct tb_port { unsigned int dma_credits; }; +/** + * struct usb4_port - USB4 port device + * @dev: Device for the port + * @port: Pointer to the lane 0 adapter + */ +struct usb4_port { + struct device dev; + struct tb_port *port; +}; + /** * tb_retimer: Thunderbolt retimer * @dev: Device for the retimer @@ -645,6 +657,7 @@ struct tb *tb_probe(struct tb_nhi *nhi); extern struct device_type tb_domain_type; extern struct device_type tb_retimer_type; extern struct device_type tb_switch_type; +extern struct device_type usb4_port_device_type; int tb_domain_init(void); void tb_domain_exit(void); @@ -1038,6 +1051,8 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, const struct tb_port *port); struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port); +int usb4_switch_add_ports(struct tb_switch *sw); +void usb4_switch_remove_ports(struct tb_switch *sw); int usb4_port_unlock(struct tb_port *port); int usb4_port_configure(struct tb_port *port); @@ -1070,6 +1085,21 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw); +static inline bool tb_is_usb4_port_device(const struct device *dev) +{ + return dev->type == &usb4_port_device_type; +} + +static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev) +{ + if (tb_is_usb4_port_device(dev)) + return container_of(dev, struct usb4_port, dev); + return NULL; +} + +struct usb4_port *usb4_port_device_add(struct tb_port *port); +void usb4_port_device_remove(struct usb4_port *usb4); + /* Keep link controller awake during update */ #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 94cc25cc6388..1f82af35328e 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -985,6 +985,60 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, return NULL; } +/** + * usb4_switch_add_ports() - Add USB4 ports for this router + * @sw: USB4 router + * + * For USB4 router finds all USB4 ports and registers devices for each. + * Can be called to any router. + * + * Return %0 in case of success and negative errno in case of failure. + */ +int usb4_switch_add_ports(struct tb_switch *sw) +{ + struct tb_port *port; + + if (tb_switch_is_icm(sw) || !tb_switch_is_usb4(sw)) + return 0; + + tb_switch_for_each_port(sw, port) { + struct usb4_port *usb4; + + if (!tb_port_is_null(port)) + continue; + if (!port->cap_usb4) + continue; + + usb4 = usb4_port_device_add(port); + if (IS_ERR(usb4)) { + usb4_switch_remove_ports(sw); + return PTR_ERR(usb4); + } + + port->usb4 = usb4; + } + + return 0; +} + +/** + * usb4_switch_remove_ports() - Removes USB4 ports from this router + * @sw: USB4 router + * + * Unregisters previously registered USB4 ports. + */ +void usb4_switch_remove_ports(struct tb_switch *sw) +{ + struct tb_port *port; + + tb_switch_for_each_port(sw, port) { + if (port->usb4) { + usb4_port_device_remove(port->usb4); + port->usb4 = NULL; + } + } +} + /** * usb4_port_unlock() - Unlock USB4 downstream port * @port: USB4 port to unlock diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c new file mode 100644 index 000000000000..520bbfd7bf33 --- /dev/null +++ b/drivers/thunderbolt/usb4_port.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB4 port device + * + * Copyright (C) 2021, Intel Corporation + * Author: Mika Westerberg + */ + +#include +#include + +#include "tb.h" + +static ssize_t link_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + struct tb_port *port = usb4->port; + struct tb *tb = port->sw->tb; + const char *link; + + if (mutex_lock_interruptible(&tb->lock)) + return -ERESTARTSYS; + + if (tb_is_upstream_port(port)) + link = port->sw->link_usb4 ? "usb4" : "tbt"; + else if (tb_port_has_remote(port)) + link = port->remote->sw->link_usb4 ? "usb4" : "tbt"; + else + link = "none"; + + mutex_unlock(&tb->lock); + + return sysfs_emit(buf, "%s\n", link); +} +static DEVICE_ATTR_RO(link); + +static struct attribute *common_attrs[] = { + &dev_attr_link.attr, + NULL +}; + +static const struct attribute_group common_group = { + .attrs = common_attrs, +}; + +static const struct attribute_group *usb4_port_device_groups[] = { + &common_group, + NULL +}; + +static void usb4_port_device_release(struct device *dev) +{ + struct usb4_port *usb4 = container_of(dev, struct usb4_port, dev); + + kfree(usb4); +} + +struct device_type usb4_port_device_type = { + .name = "usb4_port", + .groups = usb4_port_device_groups, + .release = usb4_port_device_release, +}; + +/** + * usb4_port_device_add() - Add USB4 port device + * @port: Lane 0 adapter port to add the USB4 port + * + * Creates and registers a USB4 port device for @port. Returns the new + * USB4 port device pointer or ERR_PTR() in case of error. + */ +struct usb4_port *usb4_port_device_add(struct tb_port *port) +{ + struct usb4_port *usb4; + int ret; + + usb4 = kzalloc(sizeof(*usb4), GFP_KERNEL); + if (!usb4) + return ERR_PTR(-ENOMEM); + + usb4->port = port; + usb4->dev.type = &usb4_port_device_type; + usb4->dev.parent = &port->sw->dev; + dev_set_name(&usb4->dev, "usb4_port%d", port->port); + + ret = device_register(&usb4->dev); + if (ret) { + put_device(&usb4->dev); + return ERR_PTR(ret); + } + + pm_runtime_no_callbacks(&usb4->dev); + pm_runtime_set_active(&usb4->dev); + pm_runtime_enable(&usb4->dev); + pm_runtime_set_autosuspend_delay(&usb4->dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_mark_last_busy(&usb4->dev); + pm_runtime_use_autosuspend(&usb4->dev); + + return usb4; +} + +/** + * usb4_port_device_remove() - Removes USB4 port device + * @usb4: USB4 port device + * + * Unregisters the USB4 port device from the system. The device will be + * released when the last reference is dropped. + */ +void usb4_port_device_remove(struct usb4_port *usb4) +{ + device_unregister(&usb4->dev); +} -- cgit v1.2.3 From 3fb10ea4ce86d4d06622be894099c59872e92c57 Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Thu, 1 Apr 2021 18:42:38 +0300 Subject: thunderbolt: Add support for retimer NVM upgrade when there is no link With help from platform firmware (ACPI) it is possible to power on retimers even when there is no USB4 link (e.g nothing is connected to the USB4 ports). This allows us to bring the USB4 sideband up so that we can access retimers and upgrade their NVM firmware. If the platform has support for this, we expose two additional attributes under USB4 ports: offline and rescan. These can be used to bring the port offline, rescan for the retimers and put the port online again. The retimer NVM upgrade itself works the same way than with cable connected. Signed-off-by: Rajmohan Mani Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 26 ++++ Documentation/admin-guide/thunderbolt.rst | 29 ++++ drivers/thunderbolt/retimer.c | 24 ++-- drivers/thunderbolt/switch.c | 48 ++++--- drivers/thunderbolt/tb.c | 4 +- drivers/thunderbolt/tb.h | 5 +- drivers/thunderbolt/usb4_port.c | 169 ++++++++++++++++++++++++ 7 files changed, 277 insertions(+), 28 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 3537ba1ba892..f6743dc33aac 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -297,6 +297,32 @@ Contact: Mika Westerberg Description: Returns the current link mode. Possible values are "usb4", "tbt" and "none". +What: /sys/bus/thunderbolt/devices/usb4_portX/offline +Date: Sep 2021 +KernelVersion: v5.14 +Contact: Rajmohan Mani +Description: Writing 1 to this attribute puts the USB4 port into + offline mode. Only allowed when there is nothing + connected to the port (link attribute returns "none"). + Once the port is in offline mode it does not receive any + hotplug events. This is used to update NVM firmware of + on-board retimers. Writing 0 puts the port back to + online mode. + + This attribute is only visible if the platform supports + powering on retimers when there is no cable connected. + +What: /sys/bus/thunderbolt/devices/usb4_portX/rescan +Date: Sep 2021 +KernelVersion: v5.14 +Contact: Rajmohan Mani +Description: When the USB4 port is in offline mode writing 1 to this + attribute forces rescan of the sideband for on-board + retimers. Each retimer appear under the USB4 port as if + the USB4 link was up. These retimers act in the same way + as if the cable was connected so upgrading their NVM + firmware can be done the usual way. + What: /sys/bus/thunderbolt/devices/:./device Date: Oct 2020 KernelVersion: v5.9 diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index f18e881373c4..2ed79f41a411 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -256,6 +256,35 @@ Note names of the NVMem devices ``nvm_activeN`` and ``nvm_non_activeN`` depend on the order they are registered in the NVMem subsystem. N in the name is the identifier added by the NVMem subsystem. +Upgrading on-board retimer NVM when there is no cable connected +--------------------------------------------------------------- +If the platform supports, it may be possible to upgrade the retimer NVM +firmware even when there is nothing connected to the USB4 +ports. When this is the case the ``usb4_portX`` devices have two special +attributes: ``offline`` and ``rescan``. The way to upgrade the firmware +is to first put the USB4 port into offline mode:: + + # echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/offline + +This step makes sure the port does not respond to any hotplug events, +and also ensures the retimers are powered on. The next step is to scan +for the retimers:: + + # echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan + +This enumerates and adds the on-board retimers. Now retimer NVM can be +upgraded in the same way than with cable connected (see previous +section). However, the retimer is not disconnected as we are offline +mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for +5 or more seconds before running rescan again:: + + # echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan + +This point if everything went fine, the port can be put back to +functional state again:: + + # echo 0 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/offline + Upgrading NVM when host controller is in safe mode -------------------------------------------------- If the existing NVM is not properly authenticated (or is missing) the diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 2e5188fb1150..05af0feefe84 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -401,19 +401,18 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index) /** * tb_retimer_scan() - Scan for on-board retimers under port * @port: USB4 port to scan + * @add: If true also registers found retimers * - * Tries to enumerate on-board retimers connected to @port. Found - * retimers are registered as children of @port. Does not scan for cable - * retimers for now. + * Brings the sideband into a state where retimers can be accessed. + * Then Tries to enumerate on-board retimers connected to @port. Found + * retimers are registered as children of @port if @add is set. Does + * not scan for cable retimers for now. */ -int tb_retimer_scan(struct tb_port *port) +int tb_retimer_scan(struct tb_port *port, bool add) { u32 status[TB_MAX_RETIMER_INDEX + 1] = {}; int ret, i, last_idx = 0; - if (!port->cap_usb4) - return 0; - /* * Send broadcast RT to make sure retimer indices facing this * port are set. @@ -422,6 +421,13 @@ int tb_retimer_scan(struct tb_port *port) if (ret) return ret; + /* + * Enable sideband channel for each retimer. We can do this + * regardless whether there is device connected or not. + */ + for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) + usb4_port_retimer_set_inbound_sbtx(port, i); + /* * Before doing anything else, read the authentication status. * If the retimer has it set, store it for the new retimer @@ -453,10 +459,10 @@ int tb_retimer_scan(struct tb_port *port) rt = tb_port_find_retimer(port, i); if (rt) { put_device(&rt->dev); - } else { + } else if (add) { ret = tb_retimer_add(port, i, status[i]); if (ret && ret != -EOPNOTSUPP) - return ret; + break; } } diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 7303c61a891a..dae59919e2bf 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1153,6 +1153,33 @@ static int tb_port_start_lane_initialization(struct tb_port *port) return ret == -EINVAL ? 0 : ret; } +/* + * Returns true if the port had something (router, XDomain) connected + * before suspend. + */ +static bool tb_port_resume(struct tb_port *port) +{ + bool has_remote = tb_port_has_remote(port); + + if (port->usb4) { + usb4_port_device_resume(port->usb4); + } else if (!has_remote) { + /* + * For disconnected downstream lane adapters start lane + * initialization now so we detect future connects. + * + * For XDomain start the lane initialzation now so the + * link gets re-established. + * + * This is only needed for non-USB4 ports. + */ + if (!tb_is_upstream_port(port) || port->xdomain) + tb_port_start_lane_initialization(port); + } + + return has_remote || port->xdomain; +} + /** * tb_port_is_enabled() - Is the adapter port enabled * @port: Port to check @@ -2915,22 +2942,11 @@ int tb_switch_resume(struct tb_switch *sw) /* check for surviving downstream switches */ tb_switch_for_each_port(sw, port) { - if (!tb_port_has_remote(port) && !port->xdomain) { - /* - * For disconnected downstream lane adapters - * start lane initialization now so we detect - * future connects. - */ - if (!tb_is_upstream_port(port) && tb_port_is_null(port)) - tb_port_start_lane_initialization(port); + if (!tb_port_is_null(port)) + continue; + + if (!tb_port_resume(port)) continue; - } else if (port->xdomain) { - /* - * Start lane initialization for XDomain so the - * link gets re-established. - */ - tb_port_start_lane_initialization(port); - } if (tb_wait_for_port(port, true) <= 0) { tb_port_warn(port, @@ -2939,7 +2955,7 @@ int tb_switch_resume(struct tb_switch *sw) tb_sw_set_unplugged(port->remote->sw); else if (port->xdomain) port->xdomain->is_unplugged = true; - } else if (tb_port_has_remote(port) || port->xdomain) { + } else { /* * Always unlock the port so the downstream * switch/domain is accessible. diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 7e6dc2b03bed..bc6d568dbb89 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -595,7 +595,7 @@ static void tb_scan_port(struct tb_port *port) return; } - tb_retimer_scan(port); + tb_retimer_scan(port, true); sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, tb_downstream_route(port)); @@ -662,7 +662,7 @@ static void tb_scan_port(struct tb_port *port) tb_sw_warn(sw, "failed to enable TMU\n"); /* Scan upstream retimers */ - tb_retimer_scan(upstream_port); + tb_retimer_scan(upstream_port, true); /* * Create USB 3.x tunnels only when the switch is plugged to the diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 936518adca74..341e8443a22d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -249,11 +249,13 @@ struct tb_port { * @port: Pointer to the lane 0 adapter * @can_offline: Does the port have necessary platform support to moved * it into offline mode and back + * @offline: The port is currently in offline mode */ struct usb4_port { struct device dev; struct tb_port *port; bool can_offline; + bool offline; }; /** @@ -1017,7 +1019,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd); struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, u8 depth); -int tb_retimer_scan(struct tb_port *port); +int tb_retimer_scan(struct tb_port *port, bool add); void tb_retimer_remove_all(struct tb_port *port); static inline bool tb_is_retimer(const struct device *dev) @@ -1105,6 +1107,7 @@ static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev) struct usb4_port *usb4_port_device_add(struct tb_port *port); void usb4_port_device_remove(struct usb4_port *usb4); +int usb4_port_device_resume(struct usb4_port *usb4); /* Keep link controller awake during update */ #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 520bbfd7bf33..765c74179598 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -44,8 +44,166 @@ static const struct attribute_group common_group = { .attrs = common_attrs, }; +static int usb4_port_offline(struct usb4_port *usb4) +{ + struct tb_port *port = usb4->port; + int ret; + + ret = tb_acpi_power_on_retimers(port); + if (ret) + return ret; + + ret = usb4_port_router_offline(port); + if (ret) { + tb_acpi_power_off_retimers(port); + return ret; + } + + ret = tb_retimer_scan(port, false); + if (ret) { + usb4_port_router_online(port); + tb_acpi_power_off_retimers(port); + } + + return ret; +} + +static void usb4_port_online(struct usb4_port *usb4) +{ + struct tb_port *port = usb4->port; + + usb4_port_router_online(port); + tb_acpi_power_off_retimers(port); +} + +static ssize_t offline_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + + return sysfs_emit(buf, "%d\n", usb4->offline); +} + +static ssize_t offline_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + struct tb_port *port = usb4->port; + struct tb *tb = port->sw->tb; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + pm_runtime_get_sync(&usb4->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm; + } + + if (val == usb4->offline) + goto out_unlock; + + /* Offline mode works only for ports that are not connected */ + if (tb_port_has_remote(port)) { + ret = -EBUSY; + goto out_unlock; + } + + if (val) { + ret = usb4_port_offline(usb4); + if (ret) + goto out_unlock; + } else { + usb4_port_online(usb4); + tb_retimer_remove_all(port); + } + + usb4->offline = val; + tb_port_dbg(port, "%s offline mode\n", val ? "enter" : "exit"); + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm: + pm_runtime_mark_last_busy(&usb4->dev); + pm_runtime_put_autosuspend(&usb4->dev); + + return ret ? ret : count; +} +static DEVICE_ATTR_RW(offline); + +static ssize_t rescan_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + struct tb_port *port = usb4->port; + struct tb *tb = port->sw->tb; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + if (!val) + return count; + + pm_runtime_get_sync(&usb4->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm; + } + + /* Must be in offline mode already */ + if (!usb4->offline) { + ret = -EINVAL; + goto out_unlock; + } + + tb_retimer_remove_all(port); + ret = tb_retimer_scan(port, true); + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm: + pm_runtime_mark_last_busy(&usb4->dev); + pm_runtime_put_autosuspend(&usb4->dev); + + return ret ? ret : count; +} +static DEVICE_ATTR_WO(rescan); + +static struct attribute *service_attrs[] = { + &dev_attr_offline.attr, + &dev_attr_rescan.attr, + NULL +}; + +static umode_t service_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + + /* + * Always need some platform help to cycle the modes so that + * retimers can be accessed through the sideband. + */ + return usb4->can_offline ? attr->mode : 0; +} + +static const struct attribute_group service_group = { + .attrs = service_attrs, + .is_visible = service_attr_is_visible, +}; + static const struct attribute_group *usb4_port_device_groups[] = { &common_group, + &service_group, NULL }; @@ -110,3 +268,14 @@ void usb4_port_device_remove(struct usb4_port *usb4) { device_unregister(&usb4->dev); } + +/** + * usb4_port_device_resume() - Resumes USB4 port device + * @usb4: USB4 port device + * + * Used to resume USB4 port device after sleep state. + */ +int usb4_port_device_resume(struct usb4_port *usb4) +{ + return usb4->offline ? usb4_port_offline(usb4) : 0; +} -- cgit v1.2.3 From 1cbf680f7687f55ae5a1405556519bc70d66a616 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 12 Apr 2021 15:25:08 +0300 Subject: thunderbolt: Allow router NVM authenticate separately It may be useful if the actual NVM authentication can be delayed to be run later, for instance when the user logs out. For this reason add a new NVM operation (AUHENTICATE_ONLY) that just triggers the authentication procedure over whatever was written to the NVM storage. This is not supported with Thunderbolt 1-3 devices, though. Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 5 ++- drivers/thunderbolt/switch.c | 50 ++++++++++++++++--------- drivers/thunderbolt/tb.h | 2 + drivers/thunderbolt/usb4.c | 13 ++++++- 4 files changed, 49 insertions(+), 21 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index f6743dc33aac..da580b504c87 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -213,12 +213,15 @@ Description: When new NVM image is written to the non-active NVM restarted with the new NVM firmware. If the image verification fails an error code is returned instead. - This file will accept writing values "1" or "2" + This file will accept writing values "1", "2" or "3". - Writing "1" will flush the image to the storage area and authenticate the image in one action. - Writing "2" will run some basic validation on the image and flush it to the storage area. + - Writing "3" will authenticate the image that is + currently written in the storage area. This is only + supported with USB4 devices. When read holds status of the last authentication operation if an error occurred during the process. This diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index bf4821d3bbab..83b1ef3d5d03 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -303,13 +303,23 @@ static inline int nvm_read(struct tb_switch *sw, unsigned int address, return dma_port_flash_read(sw->dma_port, address, buf, size); } -static int nvm_authenticate(struct tb_switch *sw) +static int nvm_authenticate(struct tb_switch *sw, bool auth_only) { int ret; - if (tb_switch_is_usb4(sw)) + if (tb_switch_is_usb4(sw)) { + if (auth_only) { + ret = usb4_switch_nvm_set_offset(sw, 0); + if (ret) + return ret; + } + sw->nvm->authenticating = true; return usb4_switch_nvm_authenticate(sw); + } else if (auth_only) { + return -EOPNOTSUPP; + } + sw->nvm->authenticating = true; if (!tb_route(sw)) { nvm_authenticate_start_dma_port(sw); ret = nvm_authenticate_host_dma_port(sw); @@ -1713,8 +1723,7 @@ static ssize_t nvm_authenticate_sysfs(struct device *dev, const char *buf, bool disconnect) { struct tb_switch *sw = tb_to_switch(dev); - int val; - int ret; + int val, ret; pm_runtime_get_sync(&sw->dev); @@ -1737,22 +1746,27 @@ static ssize_t nvm_authenticate_sysfs(struct device *dev, const char *buf, nvm_clear_auth_status(sw); if (val > 0) { - if (!sw->nvm->flushed) { - if (!sw->nvm->buf) { + if (val == AUTHENTICATE_ONLY) { + if (disconnect) ret = -EINVAL; - goto exit_unlock; + else + ret = nvm_authenticate(sw, true); + } else { + if (!sw->nvm->flushed) { + if (!sw->nvm->buf) { + ret = -EINVAL; + goto exit_unlock; + } + + ret = nvm_validate_and_write(sw); + if (ret || val == WRITE_ONLY) + goto exit_unlock; } - - ret = nvm_validate_and_write(sw); - if (ret || val == WRITE_ONLY) - goto exit_unlock; - } - if (val == WRITE_AND_AUTHENTICATE) { - if (disconnect) { - ret = tb_lc_force_power(sw); - } else { - sw->nvm->authenticating = true; - ret = nvm_authenticate(sw); + if (val == WRITE_AND_AUTHENTICATE) { + if (disconnect) + ret = tb_lc_force_power(sw); + else + ret = nvm_authenticate(sw, false); } } } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 863d80ad44ab..53f6bb85b178 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -61,6 +61,7 @@ struct tb_nvm { enum tb_nvm_write_ops { WRITE_AND_AUTHENTICATE = 1, WRITE_ONLY = 2, + AUTHENTICATE_ONLY = 3, }; #define TB_SWITCH_KEY_SIZE 32 @@ -1049,6 +1050,7 @@ int usb4_switch_set_sleep(struct tb_switch *sw); int usb4_switch_nvm_sector_size(struct tb_switch *sw); int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size); +int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address); int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, const void *buf, size_t size); int usb4_switch_nvm_authenticate(struct tb_switch *sw); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 8af96dbaa7a7..76d7335aa440 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -547,8 +547,17 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, usb4_switch_nvm_read_block, sw); } -static int usb4_switch_nvm_set_offset(struct tb_switch *sw, - unsigned int address) +/** + * usb4_switch_nvm_set_offset() - Set NVM write offset + * @sw: USB4 router + * @address: Start offset + * + * Explicitly sets NVM write offset. Normally when writing to NVM this + * is done automatically by usb4_switch_nvm_write(). + * + * Returns %0 in success and negative errno if there was a failure. + */ +int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address) { u32 metadata, dwaddress; u8 status = 0; -- cgit v1.2.3 From faa1c615f0bdd4f3ac5288bf2952f49dfeac916c Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Mon, 12 Apr 2021 15:29:16 +0300 Subject: thunderbolt: Add WRITE_ONLY and AUTHENTICATE_ONLY NVM operations for retimers The same way we support these two operations for USB4 routers we can extend the retimer NVM operations to support retimers also. Signed-off-by: Rajmohan Mani Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 2 +- drivers/thunderbolt/retimer.c | 51 ++++++++++++++++++------- drivers/thunderbolt/tb.h | 2 + drivers/thunderbolt/usb4.c | 15 +++++++- 4 files changed, 53 insertions(+), 17 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index da580b504c87..95c21d6c9a84 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -221,7 +221,7 @@ Description: When new NVM image is written to the non-active NVM and flush it to the storage area. - Writing "3" will authenticate the image that is currently written in the storage area. This is only - supported with USB4 devices. + supported with USB4 devices and retimers. When read holds status of the last authentication operation if an error occurred during the process. This diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 05af0feefe84..3aa790aa6500 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -103,6 +103,7 @@ static int tb_retimer_nvm_validate_and_write(struct tb_retimer *rt) unsigned int image_size, hdr_size; const u8 *buf = rt->nvm->buf; u16 ds_size, device; + int ret; image_size = rt->nvm->buf_data_size; if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE) @@ -140,8 +141,25 @@ static int tb_retimer_nvm_validate_and_write(struct tb_retimer *rt) buf += hdr_size; image_size -= hdr_size; - return usb4_port_retimer_nvm_write(rt->port, rt->index, 0, buf, - image_size); + ret = usb4_port_retimer_nvm_write(rt->port, rt->index, 0, buf, + image_size); + if (!ret) + rt->nvm->flushed = true; + + return ret; +} + +static int tb_retimer_nvm_authenticate(struct tb_retimer *rt, bool auth_only) +{ + int ret; + + if (auth_only) { + ret = usb4_port_retimer_nvm_set_offset(rt->port, rt->index, 0); + if (ret) + return ret; + } + + return usb4_port_retimer_nvm_authenticate(rt->port, rt->index); } static ssize_t device_show(struct device *dev, struct device_attribute *attr, @@ -176,8 +194,7 @@ static ssize_t nvm_authenticate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct tb_retimer *rt = tb_to_retimer(dev); - bool val; - int ret; + int val, ret; pm_runtime_get_sync(&rt->dev); @@ -191,7 +208,7 @@ static ssize_t nvm_authenticate_store(struct device *dev, goto exit_unlock; } - ret = kstrtobool(buf, &val); + ret = kstrtoint(buf, 10, &val); if (ret) goto exit_unlock; @@ -199,16 +216,22 @@ static ssize_t nvm_authenticate_store(struct device *dev, rt->auth_status = 0; if (val) { - if (!rt->nvm->buf) { - ret = -EINVAL; - goto exit_unlock; + if (val == AUTHENTICATE_ONLY) { + ret = tb_retimer_nvm_authenticate(rt, true); + } else { + if (!rt->nvm->flushed) { + if (!rt->nvm->buf) { + ret = -EINVAL; + goto exit_unlock; + } + + ret = tb_retimer_nvm_validate_and_write(rt); + if (ret || val == WRITE_ONLY) + goto exit_unlock; + } + if (val == WRITE_AND_AUTHENTICATE) + ret = tb_retimer_nvm_authenticate(rt, false); } - - ret = tb_retimer_nvm_validate_and_write(rt); - if (ret) - goto exit_unlock; - - ret = usb4_port_retimer_nvm_authenticate(rt->port, rt->index); } exit_unlock: diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 53f6bb85b178..725104c83e3d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1082,6 +1082,8 @@ int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg, const void *buf, u8 size); int usb4_port_retimer_is_last(struct tb_port *port, u8 index); int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index); +int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, + unsigned int address); int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address, const void *buf, size_t size); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 76d7335aa440..ceddbe7e9f93 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -1513,8 +1513,19 @@ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) return ret ? ret : metadata & USB4_NVM_SECTOR_SIZE_MASK; } -static int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, - unsigned int address) +/** + * usb4_port_retimer_nvm_set_offset() - Set NVM write offset + * @port: USB4 port + * @index: Retimer index + * @address: Start offset + * + * Exlicitly sets NVM write offset. Normally when writing to NVM this is + * done automatically by usb4_port_retimer_nvm_write(). + * + * Returns %0 in success and negative errno if there was a failure. + */ +int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, + unsigned int address) { u32 metadata, dwaddress; int ret; -- cgit v1.2.3 From 40c73b30546e759bedcec607fedc2d4be954508f Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 Jun 2021 00:01:03 +0200 Subject: usb: gadget: f_uac2: add adaptive sync support for capture Current f_uac2 USB OUT (aka 'capture') synchronization implements 'ASYNC' scenario which means USB Gadget has it's own freerunning clock and can update Host about real clock frequency through feedback endpoint so Host can align number of samples sent to the USB gadget to prevent overruns/underruns In case if Gadget can has no it's internal clock and can consume audio samples at any rate (for example, on the Gadget side someone records audio directly to a file, or audio samples are played through an external DAC as soon as they arrive), UAC2 spec suggests 'ADAPTIVE' synchronization type. Change UAC2 driver to make it configurable through additional 'c_sync' configfs file. Default remains 'asynchronous' with possibility to switch it to 'adaptive' Signed-off-by: Ruslan Bilovol Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20210603220104.1216001-3-jbrunet@baylibre.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uac2 | 1 + Documentation/usb/gadget-testing.rst | 1 + drivers/usb/gadget/function/f_uac2.c | 100 +++++++++++++++++++-- drivers/usb/gadget/function/u_uac2.h | 2 + 4 files changed, 95 insertions(+), 9 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2 index d4356c8b8cd6..e7e59d7fb12f 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2 @@ -8,6 +8,7 @@ Description: c_chmask capture channel mask c_srate capture sampling rate c_ssize capture sample size (bytes) + c_sync capture synchronization type (async/adaptive) p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 2085e7b24eeb..f5a12667bd41 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -728,6 +728,7 @@ The uac2 function provides these attributes in its function directory: c_chmask capture channel mask c_srate capture sampling rate c_ssize capture sample size (bytes) + c_sync capture synchronization type (async/adaptive) p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 5d63244ba319..321e6c05ba93 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -44,6 +44,7 @@ #define EPIN_EN(_opts) ((_opts)->p_chmask != 0) #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC) struct f_uac2 { struct g_audio g_audio; @@ -240,7 +241,7 @@ static struct usb_interface_descriptor std_as_out_if1_desc = { .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 1, - .bNumEndpoints = 2, + .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, .bInterfaceProtocol = UAC_VERSION_2, @@ -273,7 +274,7 @@ static struct usb_endpoint_descriptor fs_epout_desc = { .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + /* .bmAttributes = DYNAMIC */ /* .wMaxPacketSize = DYNAMIC */ .bInterval = 1, }; @@ -282,7 +283,7 @@ static struct usb_endpoint_descriptor hs_epout_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + /* .bmAttributes = DYNAMIC */ /* .wMaxPacketSize = DYNAMIC */ .bInterval = 4, }; @@ -292,7 +293,7 @@ static struct usb_endpoint_descriptor ss_epout_desc = { .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + /* .bmAttributes = DYNAMIC */ /* .wMaxPacketSize = DYNAMIC */ .bInterval = 4, }; @@ -649,7 +650,9 @@ static void setup_headers(struct f_uac2_opts *opts, headers[i++] = USBDHDR(epout_desc_comp); headers[i++] = USBDHDR(&as_iso_out_desc); - headers[i++] = USBDHDR(epin_fback_desc); + + if (EPOUT_FBACK_IN_EN(opts)) + headers[i++] = USBDHDR(epin_fback_desc); } if (EPIN_EN(opts)) { headers[i++] = USBDHDR(&std_as_in_if0_desc); @@ -820,6 +823,23 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) std_as_out_if1_desc.bInterfaceNumber = ret; uac2->as_out_intf = ret; uac2->as_out_alt = 0; + + if (EPOUT_FBACK_IN_EN(uac2_opts)) { + fs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC; + hs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC; + ss_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC; + std_as_out_if1_desc.bNumEndpoints++; + } else { + fs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE; + hs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE; + ss_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE; + } } if (EPIN_EN(uac2_opts)) { @@ -883,11 +903,14 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -ENODEV; } - agdev->in_ep_fback = usb_ep_autoconfig(gadget, + if (EPOUT_FBACK_IN_EN(uac2_opts)) { + agdev->in_ep_fback = usb_ep_autoconfig(gadget, &fs_epin_fback_desc); - if (!agdev->in_ep_fback) { - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + if (!agdev->in_ep_fback) { + dev_err(dev, "%s:%d Error!\n", + __func__, __LINE__); + return -ENODEV; + } } } @@ -1242,11 +1265,68 @@ end: \ \ CONFIGFS_ATTR(f_uac2_opts_, name) +#define UAC2_ATTRIBUTE_SYNC(name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result; \ + char *str; \ + \ + mutex_lock(&opts->lock); \ + switch (opts->name) { \ + case USB_ENDPOINT_SYNC_ASYNC: \ + str = "async"; \ + break; \ + case USB_ENDPOINT_SYNC_ADAPTIVE: \ + str = "adaptive"; \ + break; \ + default: \ + str = "unknown"; \ + break; \ + } \ + result = sprintf(page, "%s\n", str); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + if (!strncmp(page, "async", 5)) \ + opts->name = USB_ENDPOINT_SYNC_ASYNC; \ + else if (!strncmp(page, "adaptive", 8)) \ + opts->name = USB_ENDPOINT_SYNC_ADAPTIVE; \ + else { \ + ret = -EINVAL; \ + goto end; \ + } \ + \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + UAC2_ATTRIBUTE(p_chmask); UAC2_ATTRIBUTE(p_srate); UAC2_ATTRIBUTE(p_ssize); UAC2_ATTRIBUTE(c_chmask); UAC2_ATTRIBUTE(c_srate); +UAC2_ATTRIBUTE_SYNC(c_sync); UAC2_ATTRIBUTE(c_ssize); UAC2_ATTRIBUTE(req_number); @@ -1257,6 +1337,7 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_chmask, &f_uac2_opts_attr_c_srate, &f_uac2_opts_attr_c_ssize, + &f_uac2_opts_attr_c_sync, &f_uac2_opts_attr_req_number, NULL, }; @@ -1295,6 +1376,7 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->c_chmask = UAC2_DEF_CCHMASK; opts->c_srate = UAC2_DEF_CSRATE; opts->c_ssize = UAC2_DEF_CSSIZE; + opts->c_sync = UAC2_DEF_CSYNC; opts->req_number = UAC2_DEF_REQ_NUM; return &opts->func_inst; } diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index b5035711172d..13589c3c805c 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -21,6 +21,7 @@ #define UAC2_DEF_CCHMASK 0x3 #define UAC2_DEF_CSRATE 64000 #define UAC2_DEF_CSSIZE 2 +#define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC #define UAC2_DEF_REQ_NUM 2 struct f_uac2_opts { @@ -31,6 +32,7 @@ struct f_uac2_opts { int c_chmask; int c_srate; int c_ssize; + int c_sync; int req_number; bool bound; -- cgit v1.2.3 From e89bb4288378b85c82212b60dc98ecda6b3d3a70 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Fri, 4 Jun 2021 00:01:04 +0200 Subject: usb: gadget: u_audio: add real feedback implementation This adds interface between userspace and feedback endpoint to report real feedback frequency to the Host. Current implementation adds new userspace interface ALSA mixer control "Capture Pitch 1000000" (similar to aloop driver's "PCM Rate Shift 100000" mixer control) Value in PPM is chosen to have correction value agnostic of the actual HW rate, which the application is not necessarily dealing with, while still retaining a good enough precision to allow smooth clock correction on the playback side, if necessary. Similar to sound/usb/endpoint.c, a slow down is allowed up to 25%. This has no impact on the required bandwidth. Speedup correction has an impact on the bandwidth reserved for the isochronous endpoint. The default allowed speedup is 500ppm. This seems to be more than enough but, if necessary, this is configurable through a module parameter. The reserved bandwidth is rounded up to the next packet size. Usage of this new control is easy to implement in existing userspace tools like alsaloop from alsa-utils. Signed-off-by: Ruslan Bilovol Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20210603220104.1216001-4-jbrunet@baylibre.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uac2 | 1 + Documentation/usb/gadget-testing.rst | 1 + drivers/usb/gadget/function/f_uac2.c | 9 +- drivers/usb/gadget/function/u_audio.c | 124 +++++++++++++++++++-- drivers/usb/gadget/function/u_audio.h | 9 ++ drivers/usb/gadget/function/u_uac2.h | 2 + 6 files changed, 136 insertions(+), 10 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2 index e7e59d7fb12f..26fb8e9b4e61 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2 @@ -9,6 +9,7 @@ Description: c_srate capture sampling rate c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) + fb_max maximum extra bandwidth in async mode p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index f5a12667bd41..9d6276f82774 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -729,6 +729,7 @@ The uac2 function provides these attributes in its function directory: c_srate capture sampling rate c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) + fb_max maximum extra bandwidth in async mode p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 321e6c05ba93..ae29ff2b2b68 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -584,8 +584,11 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, ssize = uac2_opts->c_ssize; } + if (!is_playback && (uac2_opts->c_sync == USB_ENDPOINT_SYNC_ASYNC)) + srate = srate * (1000 + uac2_opts->fb_max) / 1000; + max_size_bw = num_channels(chmask) * ssize * - ((srate / (factor / (1 << (ep_desc->bInterval - 1)))) + 1); + DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))); ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_size_bw, max_size_ep)); @@ -957,6 +960,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->params.c_srate = uac2_opts->c_srate; agdev->params.c_ssize = uac2_opts->c_ssize; agdev->params.req_number = uac2_opts->req_number; + agdev->params.fb_max = uac2_opts->fb_max; ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); if (ret) goto err_free_descs; @@ -1329,6 +1333,7 @@ UAC2_ATTRIBUTE(c_srate); UAC2_ATTRIBUTE_SYNC(c_sync); UAC2_ATTRIBUTE(c_ssize); UAC2_ATTRIBUTE(req_number); +UAC2_ATTRIBUTE(fb_max); static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_p_chmask, @@ -1339,6 +1344,7 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_ssize, &f_uac2_opts_attr_c_sync, &f_uac2_opts_attr_req_number, + &f_uac2_opts_attr_fb_max, NULL, }; @@ -1378,6 +1384,7 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->c_ssize = UAC2_DEF_CSSIZE; opts->c_sync = UAC2_DEF_CSYNC; opts->req_number = UAC2_DEF_REQ_NUM; + opts->fb_max = UAC2_DEF_FB_MAX; return &opts->func_inst; } diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index f637ebec80b0..018dd0978995 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "u_audio.h" @@ -35,12 +36,12 @@ struct uac_rtd_params { void *rbuf; + unsigned int pitch; /* Stream pitch ratio to 1000000 */ unsigned int max_psize; /* MaxPacketSize of endpoint */ struct usb_request **reqs; struct usb_request *req_fback; /* Feedback endpoint request */ - unsigned int ffback; /* Real frequency reported by feedback endpoint */ bool fb_ep_enabled; /* if the ep is enabled */ }; @@ -75,18 +76,29 @@ static const struct snd_pcm_hardware uac_pcm_hardware = { }; static void u_audio_set_fback_frequency(enum usb_device_speed speed, - unsigned int freq, void *buf) + unsigned long long freq, + unsigned int pitch, + void *buf) { u32 ff = 0; + /* + * Because the pitch base is 1000000, the final divider here + * will be 1000 * 1000000 = 1953125 << 9 + * + * Instead of dealing with big numbers lets fold this 9 left shift + */ + if (speed == USB_SPEED_FULL) { /* * Full-speed feedback endpoints report frequency - * in samples/microframe + * in samples/frame * Format is encoded in Q10.10 left-justified in the 24 bits, * so that it has a Q10.14 format. + * + * ff = (freq << 14) / 1000 */ - ff = DIV_ROUND_UP((freq << 14), 1000); + freq <<= 5; } else { /* * High-speed feedback endpoints report frequency @@ -94,9 +106,14 @@ static void u_audio_set_fback_frequency(enum usb_device_speed speed, * Format is encoded in Q12.13 fitted into four bytes so that * the binary point is located between the second and the third * byte fromat (that is Q16.16) + * + * ff = (freq << 16) / 8000 */ - ff = DIV_ROUND_UP((freq << 13), 1000); + freq <<= 4; } + + ff = DIV_ROUND_CLOSEST_ULL((freq * pitch), 1953125); + *(__le32 *)buf = cpu_to_le32(ff); } @@ -209,8 +226,8 @@ static void u_audio_iso_fback_complete(struct usb_ep *ep, struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; int status = req->status; - unsigned long flags; /* i/f shutting down */ if (!prm->fb_ep_enabled || req->status == -ESHUTDOWN) @@ -225,7 +242,8 @@ static void u_audio_iso_fback_complete(struct usb_ep *ep, __func__, status, req->actual, req->length); u_audio_set_fback_frequency(audio_dev->gadget->speed, - prm->ffback, req->buf); + params->c_srate, prm->pitch, + req->buf); if (usb_ep_queue(ep, req, GFP_ATOMIC)) dev_err(uac->card->dev, "%d Error!\n", __LINE__); @@ -480,9 +498,10 @@ int u_audio_start_capture(struct g_audio *audio_dev) * Always start with original frequency since its deviation can't * be meauserd at start of playback */ - prm->ffback = params->c_srate; + prm->pitch = 1000000; u_audio_set_fback_frequency(audio_dev->gadget->speed, - prm->ffback, req_fback->buf); + params->c_srate, prm->pitch, + req_fback->buf); if (usb_ep_queue(ep_fback, req_fback, GFP_ATOMIC)) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -578,12 +597,82 @@ void u_audio_stop_playback(struct g_audio *audio_dev) } EXPORT_SYMBOL_GPL(u_audio_stop_playback); +static int u_audio_pitch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + unsigned int pitch_min, pitch_max; + + pitch_min = (1000 - FBACK_SLOW_MAX) * 1000; + pitch_max = (1000 + params->fb_max) * 1000; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = pitch_min; + uinfo->value.integer.max = pitch_max; + uinfo->value.integer.step = 1; + return 0; +} + +static int u_audio_pitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = prm->pitch; + + return 0; +} + +static int u_audio_pitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + unsigned int val; + unsigned int pitch_min, pitch_max; + int change = 0; + + pitch_min = (1000 - FBACK_SLOW_MAX) * 1000; + pitch_max = (1000 + params->fb_max) * 1000; + + val = ucontrol->value.integer.value[0]; + + if (val < pitch_min) + val = pitch_min; + if (val > pitch_max) + val = pitch_max; + + if (prm->pitch != val) { + prm->pitch = val; + change = 1; + } + + return change; +} + +static const struct snd_kcontrol_new u_audio_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capture Pitch 1000000", + .info = u_audio_pitch_info, + .get = u_audio_pitch_get, + .put = u_audio_pitch_put, +}, +}; + int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, const char *card_name) { struct snd_uac_chip *uac; struct snd_card *card; struct snd_pcm *pcm; + struct snd_kcontrol *kctl; struct uac_params *params; int p_chmask, c_chmask; int err; @@ -671,6 +760,23 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); + if (c_chmask && g_audio->in_ep_fback) { + strscpy(card->mixername, card_name, sizeof(card->driver)); + + kctl = snd_ctl_new1(&u_audio_controls[0], &uac->c_prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + } + strscpy(card->driver, card_name, sizeof(card->driver)); strscpy(card->shortname, card_name, sizeof(card->shortname)); sprintf(card->longname, "%s %i", card_name, card->dev->id); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h index 53e6baf55cbf..a218cdf771fe 100644 --- a/drivers/usb/gadget/function/u_audio.h +++ b/drivers/usb/gadget/function/u_audio.h @@ -11,6 +11,14 @@ #include +/* + * Same maximum frequency deviation on the slower side as in + * sound/usb/endpoint.c. Value is expressed in per-mil deviation. + * The maximum deviation on the faster side will be provided as + * parameter, as it impacts the endpoint required bandwidth. + */ +#define FBACK_SLOW_MAX 250 + struct uac_params { /* playback */ int p_chmask; /* channel mask */ @@ -23,6 +31,7 @@ struct uac_params { int c_ssize; /* sample size */ int req_number; /* number of preallocated requests */ + int fb_max; /* upper frequency drift feedback limit per-mil */ }; struct g_audio { diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index 13589c3c805c..179d3ef6a195 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -23,6 +23,7 @@ #define UAC2_DEF_CSSIZE 2 #define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC #define UAC2_DEF_REQ_NUM 2 +#define UAC2_DEF_FB_MAX 5 struct f_uac2_opts { struct usb_function_instance func_inst; @@ -34,6 +35,7 @@ struct f_uac2_opts { int c_ssize; int c_sync; int req_number; + int fb_max; bool bound; struct mutex lock; -- cgit v1.2.3 From b4e326165e21d6a11483f6a4de2174b933413554 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Wed, 9 Jun 2021 15:02:46 -0700 Subject: USB: misc: Add onboard_usb_hub driver The main issue this driver addresses is that a USB hub needs to be powered before it can be discovered. For discrete onboard hubs (an example for such a hub is the Realtek RTS5411) this is often solved by supplying the hub with an 'always-on' regulator, which is kind of a hack. Some onboard hubs may require further initialization steps, like changing the state of a GPIO or enabling a clock, which requires even more hacks. This driver creates a platform device representing the hub which performs the necessary initialization. Currently it only supports switching on a single regulator, support for multiple regulators or other actions can be added as needed. Different initialization sequences can be supported based on the compatible string. Besides performing the initialization the driver can be configured to power the hub off during system suspend. This can help to extend battery life on battery powered devices which have no requirements to keep the hub powered during suspend. The driver can also be configured to leave the hub powered when a wakeup capable USB device is connected when suspending, and power it off otherwise. Technically the driver consists of two drivers, the platform driver described above and a very thin USB driver that subclasses the generic driver. The purpose of this driver is to provide the platform driver with the USB devices corresponding to the hub(s) (a hub controller may provide multiple 'logical' hubs, e.g. one to support USB 2.0 and another for USB 3.x). Note: the current series only supports hubs connected directly to a root hub (through xhci-plat), support for other configurations could be added if needed. Co-developed-by: Ravi Chandra Sadineni Acked-by: Alan Stern Signed-off-by: Ravi Chandra Sadineni Signed-off-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20210609150159.v12.2.I7c9a1f1d6ced41dd8310e8a03da666a32364e790@changeid Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-platform-onboard-usb-hub | 8 + MAINTAINERS | 7 + drivers/usb/misc/Kconfig | 17 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/onboard_usb_hub.c | 497 +++++++++++++++++++++ include/linux/usb/onboard_hub.h | 18 + 6 files changed, 548 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub create mode 100644 drivers/usb/misc/onboard_usb_hub.c create mode 100644 include/linux/usb/onboard_hub.h (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub new file mode 100644 index 000000000000..e981d83648e6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub @@ -0,0 +1,8 @@ +What: /sys/bus/platform/devices//always_powered_in_suspend +Date: March 2021 +KernelVersion: 5.13 +Contact: Matthias Kaehlcke + linux-usb@vger.kernel.org +Description: + (RW) Controls whether the USB hub remains always powered + during system suspend or not. \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index bc0ceef87b73..56930e88e97e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13622,6 +13622,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov9734.c +ONBOARD USB HUB DRIVER +M: Matthias Kaehlcke +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml +F: drivers/usb/misc/onboard_usb_hub.c + ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 8f1144359012..f534269fbb20 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -284,3 +284,20 @@ config BRCM_USB_PINMAP This option enables support for remapping some USB external signals, which are typically on dedicated pins on the chip, to any gpio. + +config USB_ONBOARD_HUB + tristate "Onboard USB hub support" + depends on OF || COMPILE_TEST + help + Say Y here if you want to support discrete onboard USB hubs that + don't require an additional control bus for initialization, but + need some nontrivial form of initialization, such as enabling a + power regulator. An example for such a hub is the Realtek + RTS5411. + + The driver can be configured to turn off the power of the hub + during system suspend. This may reduce power consumption while + the system is suspended. + + To compile this driver as a module, choose M here: the + module will be called onboard_usb_hub. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 5f4e598573ab..2c5aec6f1b26 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o +obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c new file mode 100644 index 000000000000..ed1f5424ea5f --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (c) 2020, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct usb_device_driver onboard_hub_usbdev_driver; + +/************************** Platform driver **************************/ + +struct udev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_hub { + struct regulator *vdd; + struct device *dev; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; +}; + +static int onboard_hub_power_on(struct onboard_hub *hub) +{ + int err; + + err = regulator_enable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to enable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = true; + + return 0; +} + +static int onboard_hub_power_off(struct onboard_hub *hub) +{ + int err; + + err = regulator_disable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to disable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_hub_suspend(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + struct udev_node *node; + bool power_off; + int rc = 0; + + if (hub->always_powered_in_suspend) + return 0; + + power_off = true; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&hub->lock); + + if (power_off) + rc = onboard_hub_power_off(hub); + + return rc; +} + +static int __maybe_unused onboard_hub_resume(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + int rc = 0; + + if (!hub->is_powered_on) + rc = onboard_hub_power_on(hub); + + return rc; +} + +static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct udev_node *node; + char link_name[64]; + int ret = 0; + + mutex_lock(&hub->lock); + + if (hub->going_away) { + ret = -EINVAL; + goto unlock; + } + + node = devm_kzalloc(hub->dev, sizeof(*node), GFP_KERNEL); + if (!node) { + ret = -ENOMEM; + goto unlock; + } + + node->udev = udev; + + list_add(&node->list, &hub->udev_list); + + snprintf(link_name, sizeof(link_name), "usb_dev.%s", dev_name(&udev->dev)); + WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); + +unlock: + mutex_unlock(&hub->lock); + + return ret; +} + +static void onboard_hub_remove_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct udev_node *node; + char link_name[64]; + + snprintf(link_name, sizeof(link_name), "usb_dev.%s", dev_name(&udev->dev)); + sysfs_remove_link(&hub->dev->kobj, link_name); + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + break; + } + } + + mutex_unlock(&hub->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + hub->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_hub_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; +ATTRIBUTE_GROUPS(onboard_hub); + +static int onboard_hub_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct onboard_hub *hub; + int err; + + hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + hub->dev = dev; + mutex_init(&hub->lock); + INIT_LIST_HEAD(&hub->udev_list); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_power_on(hub); + if (err) + return err; + + /* + * The USB driver might have been detached from the USB devices by + * onboard_hub_remove(), make sure to re-attach it if needed. + */ + err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); + if (err) { + onboard_hub_power_off(hub); + return err; + } + + return 0; +} + +static int onboard_hub_remove(struct platform_device *pdev) +{ + struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); + struct udev_node *node; + struct usb_device *udev; + + hub->going_away = true; + + mutex_lock(&hub->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&hub->udev_list)) { + node = list_first_entry(&hub->udev_list, struct udev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_hub_remove_usbdev(), + * which acquires hub->lock. We must release the lock first. + */ + get_device(&udev->dev); + mutex_unlock(&hub->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&hub->lock); + } + + mutex_unlock(&hub->lock); + + return onboard_hub_power_off(hub); +} + +static const struct of_device_id onboard_hub_match[] = { + { .compatible = "usbbda,411" }, + { .compatible = "usbbda,5411" }, + {} +}; +MODULE_DEVICE_TABLE(of, onboard_hub_match); + +static bool of_is_onboard_usb_hub(const struct device_node *np) +{ + return !!of_match_node(onboard_hub_match, np); +} + +static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) +}; + +static struct platform_driver onboard_hub_driver = { + .probe = onboard_hub_probe, + .remove = onboard_hub_remove, + + .driver = { + .name = "onboard-usb-hub", + .of_match_table = onboard_hub_match, + .pm = pm_ptr(&onboard_hub_pm_ops), + .dev_groups = onboard_hub_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_REALTEK 0x0bda + +/* + * Returns the onboard_hub platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_hub *_find_onboard_hub(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + phandle ph; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + if (of_property_read_u32(dev->of_node, "companion-hub", &ph)) { + dev_err(dev, "failed to read 'companion-hub' property\n"); + return ERR_PTR(-EINVAL); + } + + np = of_find_node_by_phandle(ph); + if (!np) { + dev_err(dev, "failed to find device node for companion hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + } + + put_device(&pdev->dev); + + return dev_get_drvdata(&pdev->dev); +} + +static int onboard_hub_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_hub *hub; + int err; + + /* ignore supported hubs without device tree node */ + if (!dev->of_node) + return -ENODEV; + + hub = _find_onboard_hub(dev); + if (IS_ERR(hub)) + return PTR_ERR(hub); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_add_usbdev(hub, udev); + if (err) + return err; + + err = sysfs_create_link(&udev->dev.kobj, &hub->dev->kobj, "onboard_hub_dev"); + if (err) + dev_warn(&udev->dev, "failed to create symlink to platform device: %d\n", err); + + return 0; +} + +static void onboard_hub_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_hub *hub = dev_get_drvdata(&udev->dev); + + sysfs_remove_link(&udev->dev.kobj, "onboard_hub_dev"); + + onboard_hub_remove_usbdev(hub, udev); +} + +static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.0 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.0 */ + {}, +}; + +MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); + +static struct usb_device_driver onboard_hub_usbdev_driver = { + + .name = "onboard-usb-hub", + .probe = onboard_hub_usbdev_probe, + .disconnect = onboard_hub_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_hub_id_table, +}; + +/*** Helpers for creating/destroying platform devices for onboard hubs ***/ + +struct pdev_list_entry { + struct platform_device *pdev; + struct list_head node; +}; + +/* + * Creates a platform device for each supported onboard hub that is connected to + * the given parent hub. To keep track of the platform devices they are added to + * a list that is owned by the parent hub. + */ +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +{ + int i; + phandle ph; + struct device_node *np, *npc; + struct platform_device *pdev; + struct pdev_list_entry *pdle; + + for (i = 1; i <= parent_hub->maxchild; i++) { + np = usb_of_get_device_node(parent_hub, i); + if (!np) + continue; + + if (!of_is_onboard_usb_hub(np)) + goto node_put; + + if (of_property_read_u32(np, "companion-hub", &ph)) + goto node_put; + + npc = of_find_node_by_phandle(ph); + if (!npc) + goto node_put; + + pdev = of_find_device_by_node(npc); + of_node_put(npc); + + if (pdev) { + /* the companion hub already has a platform device, nothing to do here */ + put_device(&pdev->dev); + goto node_put; + } + + pdev = of_platform_device_create(np, NULL, &parent_hub->dev); + if (pdev) { + pdle = kzalloc(sizeof(*pdle), GFP_KERNEL); + if (!pdle) + goto node_put; + + INIT_LIST_HEAD(&pdle->node); + + pdle->pdev = pdev; + list_add(&pdle->node, pdev_list); + } else { + dev_err(&parent_hub->dev, + "failed to create platform device for onboard hub '%s'\n", + of_node_full_name(np)); + } + +node_put: + of_node_put(np); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); + +/* + * Destroys the platform devices in the given list and frees the memory associated + * with the list entry. + */ +void onboard_hub_destroy_pdevs(struct list_head *pdev_list) +{ + struct pdev_list_entry *pdle, *tmp; + + list_for_each_entry_safe(pdle, tmp, pdev_list, node) { + of_platform_device_destroy(&pdle->pdev->dev, NULL); + kfree(pdle); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); + +/************************** Driver (de)registration **************************/ + +static int __init onboard_hub_init(void) +{ + int ret; + + ret = platform_driver_register(&onboard_hub_driver); + if (ret) + return ret; + + ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); + if (ret) + platform_driver_unregister(&onboard_hub_driver); + + return ret; +} +module_init(onboard_hub_init); + +static void __exit onboard_hub_exit(void) +{ + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + platform_driver_unregister(&onboard_hub_driver); +} +module_exit(onboard_hub_exit); + +MODULE_AUTHOR("Matthias Kaehlcke "); +MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/onboard_hub.h b/include/linux/usb/onboard_hub.h new file mode 100644 index 000000000000..d9373230556e --- /dev/null +++ b/include/linux/usb/onboard_hub.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LINUX_USB_ONBOARD_HUB_H +#define __LINUX_USB_ONBOARD_HUB_H + +struct usb_device; +struct list_head; + +#if IS_ENABLED(CONFIG_USB_ONBOARD_HUB) +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list); +void onboard_hub_destroy_pdevs(struct list_head *pdev_list); +#else +static inline void onboard_hub_create_pdevs(struct usb_device *parent_hub, + struct list_head *pdev_list) {} +static inline void onboard_hub_destroy_pdevs(struct list_head *pdev_list) {} +#endif + +#endif /* __LINUX_USB_ONBOARD_HUB_H */ -- cgit v1.2.3 From 04d72afa34edd14d99db7536d22819cdbb2b2e4c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 18 Jun 2021 08:39:24 +0200 Subject: Revert "USB: misc: Add onboard_usb_hub driver" This reverts commit b4e326165e21d6a11483f6a4de2174b933413554 as the patch series is causing build issues in linux-next at the moment. Cc: Matthias Kaehlcke Link: https://lore.kernel.org/r/YMuRcrE8xlWnFSWW@google.com Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-platform-onboard-usb-hub | 8 - MAINTAINERS | 7 - drivers/usb/misc/Kconfig | 17 - drivers/usb/misc/Makefile | 1 - drivers/usb/misc/onboard_usb_hub.c | 497 --------------------- include/linux/usb/onboard_hub.h | 18 - 6 files changed, 548 deletions(-) delete mode 100644 Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub delete mode 100644 drivers/usb/misc/onboard_usb_hub.c delete mode 100644 include/linux/usb/onboard_hub.h (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub deleted file mode 100644 index e981d83648e6..000000000000 --- a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub +++ /dev/null @@ -1,8 +0,0 @@ -What: /sys/bus/platform/devices//always_powered_in_suspend -Date: March 2021 -KernelVersion: 5.13 -Contact: Matthias Kaehlcke - linux-usb@vger.kernel.org -Description: - (RW) Controls whether the USB hub remains always powered - during system suspend or not. \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index 56930e88e97e..bc0ceef87b73 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13622,13 +13622,6 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov9734.c -ONBOARD USB HUB DRIVER -M: Matthias Kaehlcke -L: linux-usb@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml -F: drivers/usb/misc/onboard_usb_hub.c - ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index f534269fbb20..8f1144359012 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -284,20 +284,3 @@ config BRCM_USB_PINMAP This option enables support for remapping some USB external signals, which are typically on dedicated pins on the chip, to any gpio. - -config USB_ONBOARD_HUB - tristate "Onboard USB hub support" - depends on OF || COMPILE_TEST - help - Say Y here if you want to support discrete onboard USB hubs that - don't require an additional control bus for initialization, but - need some nontrivial form of initialization, such as enabling a - power regulator. An example for such a hub is the Realtek - RTS5411. - - The driver can be configured to turn off the power of the hub - during system suspend. This may reduce power consumption while - the system is suspended. - - To compile this driver as a module, choose M here: the - module will be called onboard_usb_hub. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 2c5aec6f1b26..5f4e598573ab 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -32,4 +32,3 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o -obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c deleted file mode 100644 index ed1f5424ea5f..000000000000 --- a/drivers/usb/misc/onboard_usb_hub.c +++ /dev/null @@ -1,497 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for onboard USB hubs - * - * Copyright (c) 2020, Google LLC - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static struct usb_device_driver onboard_hub_usbdev_driver; - -/************************** Platform driver **************************/ - -struct udev_node { - struct usb_device *udev; - struct list_head list; -}; - -struct onboard_hub { - struct regulator *vdd; - struct device *dev; - bool always_powered_in_suspend; - bool is_powered_on; - bool going_away; - struct list_head udev_list; - struct mutex lock; -}; - -static int onboard_hub_power_on(struct onboard_hub *hub) -{ - int err; - - err = regulator_enable(hub->vdd); - if (err) { - dev_err(hub->dev, "failed to enable regulator: %d\n", err); - return err; - } - - hub->is_powered_on = true; - - return 0; -} - -static int onboard_hub_power_off(struct onboard_hub *hub) -{ - int err; - - err = regulator_disable(hub->vdd); - if (err) { - dev_err(hub->dev, "failed to disable regulator: %d\n", err); - return err; - } - - hub->is_powered_on = false; - - return 0; -} - -static int __maybe_unused onboard_hub_suspend(struct device *dev) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - struct udev_node *node; - bool power_off; - int rc = 0; - - if (hub->always_powered_in_suspend) - return 0; - - power_off = true; - - mutex_lock(&hub->lock); - - list_for_each_entry(node, &hub->udev_list, list) { - if (!device_may_wakeup(node->udev->bus->controller)) - continue; - - if (usb_wakeup_enabled_descendants(node->udev)) { - power_off = false; - break; - } - } - - mutex_unlock(&hub->lock); - - if (power_off) - rc = onboard_hub_power_off(hub); - - return rc; -} - -static int __maybe_unused onboard_hub_resume(struct device *dev) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - int rc = 0; - - if (!hub->is_powered_on) - rc = onboard_hub_power_on(hub); - - return rc; -} - -static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) -{ - struct udev_node *node; - char link_name[64]; - int ret = 0; - - mutex_lock(&hub->lock); - - if (hub->going_away) { - ret = -EINVAL; - goto unlock; - } - - node = devm_kzalloc(hub->dev, sizeof(*node), GFP_KERNEL); - if (!node) { - ret = -ENOMEM; - goto unlock; - } - - node->udev = udev; - - list_add(&node->list, &hub->udev_list); - - snprintf(link_name, sizeof(link_name), "usb_dev.%s", dev_name(&udev->dev)); - WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); - -unlock: - mutex_unlock(&hub->lock); - - return ret; -} - -static void onboard_hub_remove_usbdev(struct onboard_hub *hub, struct usb_device *udev) -{ - struct udev_node *node; - char link_name[64]; - - snprintf(link_name, sizeof(link_name), "usb_dev.%s", dev_name(&udev->dev)); - sysfs_remove_link(&hub->dev->kobj, link_name); - - mutex_lock(&hub->lock); - - list_for_each_entry(node, &hub->udev_list, list) { - if (node->udev == udev) { - list_del(&node->list); - break; - } - } - - mutex_unlock(&hub->lock); -} - -static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - - return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); -} - -static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - bool val; - int ret; - - ret = kstrtobool(buf, &val); - if (ret < 0) - return ret; - - hub->always_powered_in_suspend = val; - - return count; -} -static DEVICE_ATTR_RW(always_powered_in_suspend); - -static struct attribute *onboard_hub_attrs[] = { - &dev_attr_always_powered_in_suspend.attr, - NULL, -}; -ATTRIBUTE_GROUPS(onboard_hub); - -static int onboard_hub_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct onboard_hub *hub; - int err; - - hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - - hub->vdd = devm_regulator_get(dev, "vdd"); - if (IS_ERR(hub->vdd)) - return PTR_ERR(hub->vdd); - - hub->dev = dev; - mutex_init(&hub->lock); - INIT_LIST_HEAD(&hub->udev_list); - - dev_set_drvdata(dev, hub); - - err = onboard_hub_power_on(hub); - if (err) - return err; - - /* - * The USB driver might have been detached from the USB devices by - * onboard_hub_remove(), make sure to re-attach it if needed. - */ - err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); - if (err) { - onboard_hub_power_off(hub); - return err; - } - - return 0; -} - -static int onboard_hub_remove(struct platform_device *pdev) -{ - struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); - struct udev_node *node; - struct usb_device *udev; - - hub->going_away = true; - - mutex_lock(&hub->lock); - - /* unbind the USB devices to avoid dangling references to this device */ - while (!list_empty(&hub->udev_list)) { - node = list_first_entry(&hub->udev_list, struct udev_node, list); - udev = node->udev; - - /* - * Unbinding the driver will call onboard_hub_remove_usbdev(), - * which acquires hub->lock. We must release the lock first. - */ - get_device(&udev->dev); - mutex_unlock(&hub->lock); - device_release_driver(&udev->dev); - put_device(&udev->dev); - mutex_lock(&hub->lock); - } - - mutex_unlock(&hub->lock); - - return onboard_hub_power_off(hub); -} - -static const struct of_device_id onboard_hub_match[] = { - { .compatible = "usbbda,411" }, - { .compatible = "usbbda,5411" }, - {} -}; -MODULE_DEVICE_TABLE(of, onboard_hub_match); - -static bool of_is_onboard_usb_hub(const struct device_node *np) -{ - return !!of_match_node(onboard_hub_match, np); -} - -static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) -}; - -static struct platform_driver onboard_hub_driver = { - .probe = onboard_hub_probe, - .remove = onboard_hub_remove, - - .driver = { - .name = "onboard-usb-hub", - .of_match_table = onboard_hub_match, - .pm = pm_ptr(&onboard_hub_pm_ops), - .dev_groups = onboard_hub_groups, - }, -}; - -/************************** USB driver **************************/ - -#define VENDOR_ID_REALTEK 0x0bda - -/* - * Returns the onboard_hub platform device that is associated with the USB - * device passed as parameter. - */ -static struct onboard_hub *_find_onboard_hub(struct device *dev) -{ - struct platform_device *pdev; - struct device_node *np; - phandle ph; - - pdev = of_find_device_by_node(dev->of_node); - if (!pdev) { - if (of_property_read_u32(dev->of_node, "companion-hub", &ph)) { - dev_err(dev, "failed to read 'companion-hub' property\n"); - return ERR_PTR(-EINVAL); - } - - np = of_find_node_by_phandle(ph); - if (!np) { - dev_err(dev, "failed to find device node for companion hub\n"); - return ERR_PTR(-EINVAL); - } - - pdev = of_find_device_by_node(np); - of_node_put(np); - - if (!pdev) - return ERR_PTR(-EPROBE_DEFER); - } - - put_device(&pdev->dev); - - return dev_get_drvdata(&pdev->dev); -} - -static int onboard_hub_usbdev_probe(struct usb_device *udev) -{ - struct device *dev = &udev->dev; - struct onboard_hub *hub; - int err; - - /* ignore supported hubs without device tree node */ - if (!dev->of_node) - return -ENODEV; - - hub = _find_onboard_hub(dev); - if (IS_ERR(hub)) - return PTR_ERR(hub); - - dev_set_drvdata(dev, hub); - - err = onboard_hub_add_usbdev(hub, udev); - if (err) - return err; - - err = sysfs_create_link(&udev->dev.kobj, &hub->dev->kobj, "onboard_hub_dev"); - if (err) - dev_warn(&udev->dev, "failed to create symlink to platform device: %d\n", err); - - return 0; -} - -static void onboard_hub_usbdev_disconnect(struct usb_device *udev) -{ - struct onboard_hub *hub = dev_get_drvdata(&udev->dev); - - sysfs_remove_link(&udev->dev.kobj, "onboard_hub_dev"); - - onboard_hub_remove_usbdev(hub, udev); -} - -static const struct usb_device_id onboard_hub_id_table[] = { - { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.0 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.0 */ - {}, -}; - -MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); - -static struct usb_device_driver onboard_hub_usbdev_driver = { - - .name = "onboard-usb-hub", - .probe = onboard_hub_usbdev_probe, - .disconnect = onboard_hub_usbdev_disconnect, - .generic_subclass = 1, - .supports_autosuspend = 1, - .id_table = onboard_hub_id_table, -}; - -/*** Helpers for creating/destroying platform devices for onboard hubs ***/ - -struct pdev_list_entry { - struct platform_device *pdev; - struct list_head node; -}; - -/* - * Creates a platform device for each supported onboard hub that is connected to - * the given parent hub. To keep track of the platform devices they are added to - * a list that is owned by the parent hub. - */ -void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) -{ - int i; - phandle ph; - struct device_node *np, *npc; - struct platform_device *pdev; - struct pdev_list_entry *pdle; - - for (i = 1; i <= parent_hub->maxchild; i++) { - np = usb_of_get_device_node(parent_hub, i); - if (!np) - continue; - - if (!of_is_onboard_usb_hub(np)) - goto node_put; - - if (of_property_read_u32(np, "companion-hub", &ph)) - goto node_put; - - npc = of_find_node_by_phandle(ph); - if (!npc) - goto node_put; - - pdev = of_find_device_by_node(npc); - of_node_put(npc); - - if (pdev) { - /* the companion hub already has a platform device, nothing to do here */ - put_device(&pdev->dev); - goto node_put; - } - - pdev = of_platform_device_create(np, NULL, &parent_hub->dev); - if (pdev) { - pdle = kzalloc(sizeof(*pdle), GFP_KERNEL); - if (!pdle) - goto node_put; - - INIT_LIST_HEAD(&pdle->node); - - pdle->pdev = pdev; - list_add(&pdle->node, pdev_list); - } else { - dev_err(&parent_hub->dev, - "failed to create platform device for onboard hub '%s'\n", - of_node_full_name(np)); - } - -node_put: - of_node_put(np); - } -} -EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); - -/* - * Destroys the platform devices in the given list and frees the memory associated - * with the list entry. - */ -void onboard_hub_destroy_pdevs(struct list_head *pdev_list) -{ - struct pdev_list_entry *pdle, *tmp; - - list_for_each_entry_safe(pdle, tmp, pdev_list, node) { - of_platform_device_destroy(&pdle->pdev->dev, NULL); - kfree(pdle); - } -} -EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); - -/************************** Driver (de)registration **************************/ - -static int __init onboard_hub_init(void) -{ - int ret; - - ret = platform_driver_register(&onboard_hub_driver); - if (ret) - return ret; - - ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); - if (ret) - platform_driver_unregister(&onboard_hub_driver); - - return ret; -} -module_init(onboard_hub_init); - -static void __exit onboard_hub_exit(void) -{ - usb_deregister_device_driver(&onboard_hub_usbdev_driver); - platform_driver_unregister(&onboard_hub_driver); -} -module_exit(onboard_hub_exit); - -MODULE_AUTHOR("Matthias Kaehlcke "); -MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); -MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/onboard_hub.h b/include/linux/usb/onboard_hub.h deleted file mode 100644 index d9373230556e..000000000000 --- a/include/linux/usb/onboard_hub.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef __LINUX_USB_ONBOARD_HUB_H -#define __LINUX_USB_ONBOARD_HUB_H - -struct usb_device; -struct list_head; - -#if IS_ENABLED(CONFIG_USB_ONBOARD_HUB) -void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list); -void onboard_hub_destroy_pdevs(struct list_head *pdev_list); -#else -static inline void onboard_hub_create_pdevs(struct usb_device *parent_hub, - struct list_head *pdev_list) {} -static inline void onboard_hub_destroy_pdevs(struct list_head *pdev_list) {} -#endif - -#endif /* __LINUX_USB_ONBOARD_HUB_H */ -- cgit v1.2.3