diff options
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r-- | drivers/thunderbolt/Kconfig | 14 | ||||
-rw-r--r-- | drivers/thunderbolt/Makefile | 4 | ||||
-rw-r--r-- | drivers/thunderbolt/acpi.c | 117 | ||||
-rw-r--r-- | drivers/thunderbolt/cap.c | 136 | ||||
-rw-r--r-- | drivers/thunderbolt/ctl.c | 23 | ||||
-rw-r--r-- | drivers/thunderbolt/debugfs.c | 701 | ||||
-rw-r--r-- | drivers/thunderbolt/domain.c | 48 | ||||
-rw-r--r-- | drivers/thunderbolt/icm.c | 5 | ||||
-rw-r--r-- | drivers/thunderbolt/lc.c | 151 | ||||
-rw-r--r-- | drivers/thunderbolt/nhi.c | 90 | ||||
-rw-r--r-- | drivers/thunderbolt/nhi_ops.c | 31 | ||||
-rw-r--r-- | drivers/thunderbolt/quirks.c | 2 | ||||
-rw-r--r-- | drivers/thunderbolt/switch.c | 216 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.c | 207 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.h | 160 | ||||
-rw-r--r-- | drivers/thunderbolt/tb_msgs.h | 1 | ||||
-rw-r--r-- | drivers/thunderbolt/tb_regs.h | 34 | ||||
-rw-r--r-- | drivers/thunderbolt/test.c | 13 | ||||
-rw-r--r-- | drivers/thunderbolt/usb4.c | 251 |
19 files changed, 1970 insertions, 234 deletions
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 354e61c0f2e5..7fc058f81d00 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -16,7 +16,19 @@ menuconfig USB4 To compile this driver a module, choose M here. The module will be called thunderbolt. +if USB4 + +config USB4_DEBUGFS_WRITE + bool "Enable write by debugfs to configuration spaces (DANGEROUS)" + help + Enables writing to device configuration registers through + debugfs interface. + + Only enable this if you know what you are doing! Never enable + this for production systems or distro kernels. + config USB4_KUNIT_TEST bool "KUnit tests" depends on KUNIT=y - depends on USB4=y + +endif # USB4 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 4ab5bfad7bfd..571537371072 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -4,4 +4,6 @@ thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o ee 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 -obj-${CONFIG_USB4_KUNIT_TEST} += test.o +thunderbolt-${CONFIG_ACPI} += acpi.o +thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o +thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c new file mode 100644 index 000000000000..a5f988a9f948 --- /dev/null +++ b/drivers/thunderbolt/acpi.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI support + * + * Copyright (C) 2020, Intel Corporation + * Author: Mika Westerberg <mika.westerberg@linux.intel.com> + */ + +#include <linux/acpi.h> + +#include "tb.h" + +static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, + void **return_value) +{ + struct fwnode_reference_args args; + struct fwnode_handle *fwnode; + struct tb_nhi *nhi = data; + struct acpi_device *adev; + struct pci_dev *pdev; + struct device *dev; + int ret; + + if (acpi_bus_get_device(handle, &adev)) + return AE_OK; + + fwnode = acpi_fwnode_handle(adev); + ret = fwnode_property_get_reference_args(fwnode, "usb4-host-interface", + NULL, 0, 0, &args); + if (ret) + return AE_OK; + + /* It needs to reference this NHI */ + if (nhi->pdev->dev.fwnode != args.fwnode) + goto out_put; + + /* + * Try to find physical device walking upwards to the hierarcy. + * We need to do this because the xHCI driver might not yet be + * bound so the USB3 SuperSpeed ports are not yet created. + */ + dev = acpi_get_first_physical_node(adev); + while (!dev) { + adev = adev->parent; + if (!adev) + break; + dev = acpi_get_first_physical_node(adev); + } + + if (!dev) + goto out_put; + + /* + * Check that the device is PCIe. This is because USB3 + * SuperSpeed ports have this property and they are not power + * managed with the xHCI and the SuperSpeed hub so we create the + * link from xHCI instead. + */ + while (!dev_is_pci(dev)) + dev = dev->parent; + + if (!dev) + goto out_put; + + /* + * Check that this actually matches the type of device we + * expect. It should either be xHCI or PCIe root/downstream + * port. + */ + pdev = to_pci_dev(dev); + if (pdev->class == PCI_CLASS_SERIAL_USB_XHCI || + (pci_is_pcie(pdev) && + (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT || + pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM))) { + const struct device_link *link; + + link = device_link_add(&pdev->dev, &nhi->pdev->dev, + DL_FLAG_AUTOREMOVE_SUPPLIER | + DL_FLAG_PM_RUNTIME); + if (link) { + dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_name(&pdev->dev)); + } else { + dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_name(&pdev->dev)); + } + } + +out_put: + fwnode_handle_put(args.fwnode); + return AE_OK; +} + +/** + * tb_acpi_add_links() - Add device links based on ACPI description + * @nhi: Pointer to NHI + * + * Goes over ACPI namespace finding tunneled ports that reference to + * @nhi ACPI node. For each reference a device link is added. The link + * is automatically removed by the driver core. + */ +void tb_acpi_add_links(struct tb_nhi *nhi) +{ + acpi_status status; + + if (!has_acpi_companion(&nhi->pdev->dev)) + return; + + /* + * Find all devices that have usb4-host-controller interface + * property that references to this NHI. + */ + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32, + tb_acpi_add_link, NULL, nhi, NULL); + if (ACPI_FAILURE(status)) + dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n"); +} diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index 19db6cdc5b70..6f571e912cf2 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -15,14 +15,6 @@ #define VSE_CAP_OFFSET_MAX 0xffff #define TMU_ACCESS_EN BIT(20) -struct tb_cap_any { - union { - struct tb_cap_basic basic; - struct tb_cap_extended_short extended_short; - struct tb_cap_extended_long extended_long; - }; -} __packed; - static int tb_port_enable_tmu(struct tb_port *port, bool enable) { struct tb_switch *sw = port->sw; @@ -67,23 +59,50 @@ static void tb_port_dummy_read(struct tb_port *port) } } +/** + * tb_port_next_cap() - Return next capability in the linked list + * @port: Port to find the capability for + * @offset: Previous capability offset (%0 for start) + * + * Returns dword offset of the next capability in port config space + * capability list and returns it. Passing %0 returns the first entry in + * the capability list. If no next capability is found returns %0. In case + * of failure returns negative errno. + */ +int tb_port_next_cap(struct tb_port *port, unsigned int offset) +{ + struct tb_cap_any header; + int ret; + + if (!offset) + return port->config.first_cap_offset; + + ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); + if (ret) + return ret; + + return header.basic.next; +} + static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) { - u32 offset = 1; + int offset = 0; do { struct tb_cap_any header; int ret; + offset = tb_port_next_cap(port, offset); + if (offset < 0) + return offset; + ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); if (ret) return ret; if (header.basic.cap == cap) return offset; - - offset = header.basic.next; - } while (offset); + } while (offset > 0); return -ENOENT; } @@ -114,6 +133,50 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) } /** + * tb_switch_next_cap() - Return next capability in the linked list + * @sw: Switch to find the capability for + * @offset: Previous capability offset (%0 for start) + * + * Finds dword offset of the next capability in router config space + * capability list and returns it. Passing %0 returns the first entry in + * the capability list. If no next capability is found returns %0. In case + * of failure returns negative errno. + */ +int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) +{ + struct tb_cap_any header; + int ret; + + if (!offset) + return sw->config.first_cap_offset; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); + if (ret) + return ret; + + switch (header.basic.cap) { + case TB_SWITCH_CAP_TMU: + ret = header.basic.next; + break; + + case TB_SWITCH_CAP_VSE: + if (!header.extended_short.length) + ret = header.extended_long.next; + else + ret = header.extended_short.next; + break; + + default: + tb_sw_dbg(sw, "unknown capability %#x at %#x\n", + header.basic.cap, offset); + ret = -EINVAL; + break; + } + + return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret; +} + +/** * tb_switch_find_cap() - Find switch capability * @sw Switch to find the capability for * @cap: Capability to look @@ -124,21 +187,23 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) */ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) { - int offset = sw->config.first_cap_offset; + int offset = 0; - while (offset > 0 && offset < CAP_OFFSET_MAX) { + do { struct tb_cap_any header; int ret; + offset = tb_switch_next_cap(sw, offset); + if (offset < 0) + return offset; + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); if (ret) return ret; if (header.basic.cap == cap) return offset; - - offset = header.basic.next; - } + } while (offset); return -ENOENT; } @@ -155,37 +220,24 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) */ int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) { - struct tb_cap_any header; - int offset; + int offset = 0; - offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); - if (offset < 0) - return offset; - - while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { + do { + struct tb_cap_any header; int ret; - ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); + offset = tb_switch_next_cap(sw, offset); + if (offset < 0) + return offset; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); if (ret) return ret; - /* - * Extended vendor specific capabilities come in two - * flavors: short and long. The latter is used when - * offset is over 0xff. - */ - if (offset >= CAP_OFFSET_MAX) { - if (header.extended_long.vsec_id == vsec) - return offset; - offset = header.extended_long.next; - } else { - if (header.extended_short.vsec_id == vsec) - return offset; - if (!header.extended_short.length) - return -ENOENT; - offset = header.extended_short.next; - } - } + if (header.extended_short.cap == TB_SWITCH_CAP_VSE && + header.extended_short.vsec_id == vsec) + return offset; + } while (offset); return -ENOENT; } diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 394a23ce6ca4..9894b8f63064 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -219,6 +219,7 @@ static int check_config_address(struct tb_cfg_address addr, static struct tb_cfg_result decode_error(const struct ctl_pkg *response) { struct cfg_error_pkg *pkg = response->buffer; + struct tb_ctl *ctl = response->ctl; struct tb_cfg_result res = { 0 }; res.response_route = tb_cfg_get_route(&pkg->header); res.response_port = 0; @@ -227,9 +228,13 @@ static struct tb_cfg_result decode_error(const struct ctl_pkg *response) if (res.err) return res; - WARN(pkg->zero1, "pkg->zero1 is %#x\n", pkg->zero1); - WARN(pkg->zero2, "pkg->zero1 is %#x\n", pkg->zero1); - WARN(pkg->zero3, "pkg->zero1 is %#x\n", pkg->zero1); + if (pkg->zero1) + tb_ctl_warn(ctl, "pkg->zero1 is %#x\n", pkg->zero1); + if (pkg->zero2) + tb_ctl_warn(ctl, "pkg->zero2 is %#x\n", pkg->zero2); + if (pkg->zero3) + tb_ctl_warn(ctl, "pkg->zero3 is %#x\n", pkg->zero3); + res.err = 1; res.tb_error = pkg->error; res.response_port = pkg->port; @@ -266,9 +271,8 @@ static void tb_cfg_print_error(struct tb_ctl *ctl, * Invalid cfg_space/offset/length combination in * cfg_read/cfg_write. */ - tb_ctl_WARN(ctl, - "CFG_ERROR(%llx:%x): Invalid config space or offset\n", - res->response_route, res->response_port); + tb_ctl_dbg(ctl, "%llx:%x: invalid config space or offset\n", + res->response_route, res->response_port); return; case TB_CFG_ERROR_NO_SUCH_PORT: /* @@ -283,6 +287,10 @@ static void tb_cfg_print_error(struct tb_ctl *ctl, tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Route contains a loop\n", res->response_route, res->response_port); return; + case TB_CFG_ERROR_LOCK: + tb_ctl_warn(ctl, "%llx:%x: downstream port is locked\n", + res->response_route, res->response_port); + return; default: /* 5,6,7,9 and 11 are also valid error codes */ tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Unknown error\n", @@ -951,6 +959,9 @@ static int tb_cfg_get_error(struct tb_ctl *ctl, enum tb_cfg_space space, return -ENODEV; tb_cfg_print_error(ctl, res); + + if (res->tb_error == TB_CFG_ERROR_LOCK) + return -EACCES; return -EIO; } diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c new file mode 100644 index 000000000000..3680b2784ea1 --- /dev/null +++ b/drivers/thunderbolt/debugfs.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Debugfs interface + * + * Copyright (C) 2020, Intel Corporation + * Authors: Gil Fine <gil.fine@intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + */ + +#include <linux/debugfs.h> +#include <linux/pm_runtime.h> + +#include "tb.h" + +#define PORT_CAP_PCIE_LEN 1 +#define PORT_CAP_POWER_LEN 2 +#define PORT_CAP_LANE_LEN 3 +#define PORT_CAP_USB3_LEN 5 +#define PORT_CAP_DP_LEN 8 +#define PORT_CAP_TMU_LEN 8 +#define PORT_CAP_BASIC_LEN 9 +#define PORT_CAP_USB4_LEN 20 + +#define SWITCH_CAP_TMU_LEN 26 +#define SWITCH_CAP_BASIC_LEN 27 + +#define PATH_LEN 2 + +#define COUNTER_SET_LEN 3 + +#define DEBUGFS_ATTR(__space, __write) \ +static int __space ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, __space ## _show, inode->i_private); \ +} \ + \ +static const struct file_operations __space ## _fops = { \ + .owner = THIS_MODULE, \ + .open = __space ## _open, \ + .release = single_release, \ + .read = seq_read, \ + .write = __write, \ + .llseek = seq_lseek, \ +} + +#define DEBUGFS_ATTR_RO(__space) \ + DEBUGFS_ATTR(__space, NULL) + +#define DEBUGFS_ATTR_RW(__space) \ + DEBUGFS_ATTR(__space, __space ## _write) + +static struct dentry *tb_debugfs_root; + +static void *validate_and_copy_from_user(const void __user *user_buf, + size_t *count) +{ + size_t nbytes; + void *buf; + + if (!*count) + return ERR_PTR(-EINVAL); + + if (!access_ok(user_buf, *count)) + return ERR_PTR(-EFAULT); + + buf = (void *)get_zeroed_page(GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + nbytes = min_t(size_t, *count, PAGE_SIZE); + if (copy_from_user(buf, user_buf, nbytes)) { + free_page((unsigned long)buf); + return ERR_PTR(-EFAULT); + } + + *count = nbytes; + return buf; +} + +static bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len, + int long_fmt_len) +{ + char *token; + u32 v[5]; + int ret; + + token = strsep(line, "\n"); + if (!token) + return false; + + /* + * For Adapter/Router configuration space: + * Short format is: offset value\n + * v[0] v[1] + * Long format as produced from the read side: + * offset relative_offset cap_id vs_cap_id value\n + * v[0] v[1] v[2] v[3] v[4] + * + * For Counter configuration space: + * Short format is: offset\n + * v[0] + * Long format as produced from the read side: + * offset relative_offset counter_id value\n + * v[0] v[1] v[2] v[3] + */ + ret = sscanf(token, "%i %i %i %i %i", &v[0], &v[1], &v[2], &v[3], &v[4]); + /* In case of Counters, clear counter, "val" content is NA */ + if (ret == short_fmt_len) { + *offs = v[0]; + *val = v[short_fmt_len - 1]; + return true; + } else if (ret == long_fmt_len) { + *offs = v[0]; + *val = v[long_fmt_len - 1]; + return true; + } + + return false; +} + +#if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE) +static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct tb *tb = sw->tb; + char *line, *buf; + u32 val, offset; + int ret = 0; + + buf = validate_and_copy_from_user(user_buf, &count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + pm_runtime_get_sync(&sw->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out; + } + + /* User did hardware changes behind the driver's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + line = buf; + while (parse_line(&line, &offset, &val, 2, 5)) { + if (port) + ret = tb_port_write(port, &val, TB_CFG_PORT, offset, 1); + else + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1); + if (ret) + break; + } + + mutex_unlock(&tb->lock); + +out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + free_page((unsigned long)buf); + + return ret < 0 ? ret : count; +} + +static ssize_t port_regs_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct tb_port *port = s->private; + + return regs_write(port->sw, port, user_buf, count, ppos); +} + +static ssize_t switch_regs_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct tb_switch *sw = s->private; + + return regs_write(sw, NULL, user_buf, count, ppos); +} +#define DEBUGFS_MODE 0600 +#else +#define port_regs_write NULL +#define switch_regs_write NULL +#define DEBUGFS_MODE 0400 +#endif + +static int port_clear_all_counters(struct tb_port *port) +{ + u32 *buf; + int ret; + + buf = kcalloc(COUNTER_SET_LEN * port->config.max_counters, sizeof(u32), + GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = tb_port_write(port, buf, TB_CFG_COUNTERS, 0, + COUNTER_SET_LEN * port->config.max_counters); + kfree(buf); + + return ret; +} + +static ssize_t counters_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct tb_port *port = s->private; + struct tb_switch *sw = port->sw; + struct tb *tb = port->sw->tb; + char *buf; + int ret; + + buf = validate_and_copy_from_user(user_buf, &count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + pm_runtime_get_sync(&sw->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out; + } + + /* If written delimiter only, clear all counters in one shot */ + if (buf[0] == '\n') { + ret = port_clear_all_counters(port); + } else { + char *line = buf; + u32 val, offset; + + ret = -EINVAL; + while (parse_line(&line, &offset, &val, 1, 4)) { + ret = tb_port_write(port, &val, TB_CFG_COUNTERS, + offset, 1); + if (ret) + break; + } + } + + mutex_unlock(&tb->lock); + +out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + free_page((unsigned long)buf); + + return ret < 0 ? ret : count; +} + +static void cap_show(struct seq_file *s, struct tb_switch *sw, + struct tb_port *port, unsigned int cap, u8 cap_id, + u8 vsec_id, int length) +{ + int ret, offset = 0; + + while (length > 0) { + int i, dwords = min(length, TB_MAX_CONFIG_RW_LENGTH); + u32 data[TB_MAX_CONFIG_RW_LENGTH]; + + if (port) + ret = tb_port_read(port, data, TB_CFG_PORT, cap + offset, + dwords); + else + ret = tb_sw_read(sw, data, TB_CFG_SWITCH, cap + offset, dwords); + if (ret) { + seq_printf(s, "0x%04x <not accessible>\n", + cap + offset); + if (dwords > 1) + seq_printf(s, "0x%04x ...\n", cap + offset + 1); + return; + } + + for (i = 0; i < dwords; i++) { + seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n", + cap + offset + i, offset + i, + cap_id, vsec_id, data[i]); + } + + length -= dwords; + offset += dwords; + } +} + +static void port_cap_show(struct tb_port *port, struct seq_file *s, + unsigned int cap) +{ + struct tb_cap_any header; + u8 vsec_id = 0; + size_t length; + int ret; + + ret = tb_port_read(port, &header, TB_CFG_PORT, cap, 1); + if (ret) { + seq_printf(s, "0x%04x <capability read failed>\n", cap); + return; + } + + switch (header.basic.cap) { + case TB_PORT_CAP_PHY: + length = PORT_CAP_LANE_LEN; + break; + + case TB_PORT_CAP_TIME1: + length = PORT_CAP_TMU_LEN; + break; + + case TB_PORT_CAP_POWER: + length = PORT_CAP_POWER_LEN; + break; + + case TB_PORT_CAP_ADAP: + if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) { + length = PORT_CAP_PCIE_LEN; + } else if (tb_port_is_dpin(port) || tb_port_is_dpout(port)) { + length = PORT_CAP_DP_LEN; + } else if (tb_port_is_usb3_down(port) || + tb_port_is_usb3_up(port)) { + length = PORT_CAP_USB3_LEN; + } else { + seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n", + cap, header.basic.cap); + return; + } + break; + + case TB_PORT_CAP_VSE: + if (!header.extended_short.length) { + ret = tb_port_read(port, (u32 *)&header + 1, TB_CFG_PORT, + cap + 1, 1); + if (ret) { + seq_printf(s, "0x%04x <capability read failed>\n", + cap + 1); + return; + } + length = header.extended_long.length; + vsec_id = header.extended_short.vsec_id; + } else { + length = header.extended_short.length; + vsec_id = header.extended_short.vsec_id; + /* + * Ice Lake and Tiger Lake do not implement the + * full length of the capability, only first 32 + * dwords so hard-code it here. + */ + if (!vsec_id && + (tb_switch_is_ice_lake(port->sw) || + tb_switch_is_tiger_lake(port->sw))) + length = 32; + } + break; + + case TB_PORT_CAP_USB4: + length = PORT_CAP_USB4_LEN; + break; + + default: + seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n", + cap, header.basic.cap); + return; + } + + cap_show(s, NULL, port, cap, header.basic.cap, vsec_id, length); +} + +static void port_caps_show(struct tb_port *port, struct seq_file *s) +{ + int cap; + + cap = tb_port_next_cap(port, 0); + while (cap > 0) { + port_cap_show(port, s, cap); + cap = tb_port_next_cap(port, cap); + } +} + +static int port_basic_regs_show(struct tb_port *port, struct seq_file *s) +{ + u32 data[PORT_CAP_BASIC_LEN]; + int ret, i; + + ret = tb_port_read(port, data, TB_CFG_PORT, 0, ARRAY_SIZE(data)); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(data); i++) + seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]); + + return 0; +} + +static int port_regs_show(struct seq_file *s, void *not_used) +{ + struct tb_port *port = s->private; + struct tb_switch *sw = port->sw; + struct tb *tb = sw->tb; + int ret; + + pm_runtime_get_sync(&sw->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm_put; + } + + seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n"); + + ret = port_basic_regs_show(port, s); + if (ret) + goto out_unlock; + + port_caps_show(port, s); + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm_put: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + + return ret; +} +DEBUGFS_ATTR_RW(port_regs); + +static void switch_cap_show(struct tb_switch *sw, struct seq_file *s, + unsigned int cap) +{ + struct tb_cap_any header; + int ret, length; + u8 vsec_id = 0; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, cap, 1); + if (ret) { + seq_printf(s, "0x%04x <capability read failed>\n", cap); + return; + } + + if (header.basic.cap == TB_SWITCH_CAP_VSE) { + if (!header.extended_short.length) { + ret = tb_sw_read(sw, (u32 *)&header + 1, TB_CFG_SWITCH, + cap + 1, 1); + if (ret) { + seq_printf(s, "0x%04x <capability read failed>\n", + cap + 1); + return; + } + length = header.extended_long.length; + } else { + length = header.extended_short.length; + } + vsec_id = header.extended_short.vsec_id; + } else { + if (header.basic.cap == TB_SWITCH_CAP_TMU) { + length = SWITCH_CAP_TMU_LEN; + } else { + seq_printf(s, "0x%04x <unknown capability 0x%02x>\n", + cap, header.basic.cap); + return; + } + } + + cap_show(s, sw, NULL, cap, header.basic.cap, vsec_id, length); +} + +static void switch_caps_show(struct tb_switch *sw, struct seq_file *s) +{ + int cap; + + cap = tb_switch_next_cap(sw, 0); + while (cap > 0) { + switch_cap_show(sw, s, cap); + cap = tb_switch_next_cap(sw, cap); + } +} + +static int switch_basic_regs_show(struct tb_switch *sw, struct seq_file *s) +{ + u32 data[SWITCH_CAP_BASIC_LEN]; + size_t dwords; + int ret, i; + + /* Only USB4 has the additional registers */ + if (tb_switch_is_usb4(sw)) + dwords = ARRAY_SIZE(data); + else + dwords = 7; + + ret = tb_sw_read(sw, data, TB_CFG_SWITCH, 0, dwords); + if (ret) + return ret; + + for (i = 0; i < dwords; i++) + seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]); + + return 0; +} + +static int switch_regs_show(struct seq_file *s, void *not_used) +{ + struct tb_switch *sw = s->private; + struct tb *tb = sw->tb; + int ret; + + pm_runtime_get_sync(&sw->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm_put; + } + + seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n"); + + ret = switch_basic_regs_show(sw, s); + if (ret) + goto out_unlock; + + switch_caps_show(sw, s); + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm_put: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + + return ret; +} +DEBUGFS_ATTR_RW(switch_regs); + +static int path_show_one(struct tb_port *port, struct seq_file *s, int hopid) +{ + u32 data[PATH_LEN]; + int ret, i; + + ret = tb_port_read(port, data, TB_CFG_HOPS, hopid * PATH_LEN, + ARRAY_SIZE(data)); + if (ret) { + seq_printf(s, "0x%04x <not accessible>\n", hopid * PATH_LEN); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data); i++) { + seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n", + hopid * PATH_LEN + i, i, hopid, data[i]); + } + + return 0; +} + +static int path_show(struct seq_file *s, void *not_used) +{ + struct tb_port *port = s->private; + struct tb_switch *sw = port->sw; + struct tb *tb = sw->tb; + int start, i, ret = 0; + + pm_runtime_get_sync(&sw->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm_put; + } + + seq_puts(s, "# offset relative_offset in_hop_id value\n"); + + /* NHI and lane adapters have entry for path 0 */ + if (tb_port_is_null(port) || tb_port_is_nhi(port)) { + ret = path_show_one(port, s, 0); + if (ret) + goto out_unlock; + } + + start = tb_port_is_nhi(port) ? 1 : TB_PATH_MIN_HOPID; + + for (i = start; i <= port->config.max_in_hop_id; i++) { + ret = path_show_one(port, s, i); + if (ret) + break; + } + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm_put: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + + return ret; +} +DEBUGFS_ATTR_RO(path); + +static int counter_set_regs_show(struct tb_port *port, struct seq_file *s, + int counter) +{ + u32 data[COUNTER_SET_LEN]; + int ret, i; + + ret = tb_port_read(port, data, TB_CFG_COUNTERS, + counter * COUNTER_SET_LEN, ARRAY_SIZE(data)); + if (ret) { + seq_printf(s, "0x%04x <not accessible>\n", + counter * COUNTER_SET_LEN); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data); i++) { + seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n", + counter * COUNTER_SET_LEN + i, i, counter, data[i]); + } + + return 0; +} + +static int counters_show(struct seq_file *s, void *not_used) +{ + struct tb_port *port = s->private; + struct tb_switch *sw = port->sw; + struct tb *tb = sw->tb; + int i, ret = 0; + + pm_runtime_get_sync(&sw->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out; + } + + seq_puts(s, "# offset relative_offset counter_id value\n"); + + for (i = 0; i < port->config.max_counters; i++) { + ret = counter_set_regs_show(port, s, i); + if (ret) + break; + } + + mutex_unlock(&tb->lock); + +out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + + return ret; +} +DEBUGFS_ATTR_RW(counters); + +/** + * tb_switch_debugfs_init() - Add debugfs entries for router + * @sw: Pointer to the router + * + * Adds debugfs directories and files for given router. + */ +void tb_switch_debugfs_init(struct tb_switch *sw) +{ + struct dentry *debugfs_dir; + struct tb_port *port; + + debugfs_dir = debugfs_create_dir(dev_name(&sw->dev), tb_debugfs_root); + sw->debugfs_dir = debugfs_dir; + debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir, sw, + &switch_regs_fops); + + tb_switch_for_each_port(sw, port) { + struct dentry *debugfs_dir; + char dir_name[10]; + + if (port->disabled) + continue; + if (port->config.type == TB_TYPE_INACTIVE) + continue; + + snprintf(dir_name, sizeof(dir_name), "port%d", port->port); + debugfs_dir = debugfs_create_dir(dir_name, sw->debugfs_dir); + debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir, + port, &port_regs_fops); + debugfs_create_file("path", 0400, debugfs_dir, port, + &path_fops); + if (port->config.counters_support) + debugfs_create_file("counters", 0600, debugfs_dir, port, + &counters_fops); + } +} + +/** + * tb_switch_debugfs_remove() - Remove all router debugfs entries + * @sw: Pointer to the router + * + * Removes all previously added debugfs entries under this router. + */ +void tb_switch_debugfs_remove(struct tb_switch *sw) +{ + debugfs_remove_recursive(sw->debugfs_dir); +} + +void tb_debugfs_init(void) +{ + tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL); +} + +void tb_debugfs_exit(void) +{ + debugfs_remove_recursive(tb_debugfs_root); +} diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index bba4cbfa9759..f0de94f7acbf 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -275,7 +275,7 @@ static struct attribute *domain_attrs[] = { static umode_t domain_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct tb *tb = container_of(dev, struct tb, dev); if (attr == &dev_attr_boot_acl.attr) { @@ -455,6 +455,8 @@ int tb_domain_add(struct tb *tb) /* This starts event processing */ mutex_unlock(&tb->lock); + device_init_wakeup(&tb->dev, true); + pm_runtime_no_callbacks(&tb->dev); pm_runtime_set_active(&tb->dev); pm_runtime_enable(&tb->dev); @@ -544,6 +546,33 @@ int tb_domain_suspend(struct tb *tb) return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0; } +int tb_domain_freeze_noirq(struct tb *tb) +{ + int ret = 0; + + mutex_lock(&tb->lock); + if (tb->cm_ops->freeze_noirq) + ret = tb->cm_ops->freeze_noirq(tb); + if (!ret) + tb_ctl_stop(tb->ctl); + mutex_unlock(&tb->lock); + + return ret; +} + +int tb_domain_thaw_noirq(struct tb *tb) +{ + int ret = 0; + + mutex_lock(&tb->lock); + tb_ctl_start(tb->ctl); + if (tb->cm_ops->thaw_noirq) + ret = tb->cm_ops->thaw_noirq(tb); + mutex_unlock(&tb->lock); + + return ret; +} + void tb_domain_complete(struct tb *tb) { if (tb->cm_ops->complete) @@ -798,12 +827,23 @@ int tb_domain_init(void) { int ret; + tb_test_init(); + + tb_debugfs_init(); ret = tb_xdomain_init(); if (ret) - return ret; + goto err_debugfs; ret = bus_register(&tb_bus_type); if (ret) - tb_xdomain_exit(); + goto err_xdomain; + + return 0; + +err_xdomain: + tb_xdomain_exit(); +err_debugfs: + tb_debugfs_exit(); + tb_test_exit(); return ret; } @@ -814,4 +854,6 @@ void tb_domain_exit(void) ida_destroy(&tb_domain_ida); tb_nvm_exit(); tb_xdomain_exit(); + tb_debugfs_exit(); + tb_test_exit(); } diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index ffcc8c3459e5..b51fc3f62b1f 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -1635,11 +1635,14 @@ static void icm_icl_rtd3_veto(struct tb *tb, const struct icm_pkg_header *hdr) static bool icm_tgl_is_supported(struct tb *tb) { + u32 val; + /* * If the firmware is not running use software CM. This platform * should fully support both. */ - return icm_firmware_running(tb->nhi); + val = ioread32(tb->nhi->iobase + REG_FW_STS); + return !!(val & REG_FW_STS_NVM_AUTH_DONE); } static void icm_handle_notification(struct work_struct *work) diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c index 19be627d090f..41e6c738f6c8 100644 --- a/drivers/thunderbolt/lc.c +++ b/drivers/thunderbolt/lc.c @@ -45,7 +45,7 @@ static int find_port_lc_cap(struct tb_port *port) return sw->cap_lc + start + phys * size; } -static int tb_lc_configure_lane(struct tb_port *port, bool configure) +static int tb_lc_set_port_configured(struct tb_port *port, bool configured) { bool upstream = tb_is_upstream_port(port); struct tb_switch *sw = port->sw; @@ -69,7 +69,7 @@ static int tb_lc_configure_lane(struct tb_port *port, bool configure) else lane = TB_LC_SX_CTRL_L2C; - if (configure) { + if (configured) { ctrl |= lane; if (upstream) ctrl |= TB_LC_SX_CTRL_UPSTREAM; @@ -83,55 +83,146 @@ static int tb_lc_configure_lane(struct tb_port *port, bool configure) } /** - * tb_lc_configure_link() - Let LC know about configured link - * @sw: Switch that is being added + * tb_lc_configure_port() - Let LC know about configured port + * @port: Port that is set as configured * - * Informs LC of both parent switch and @sw that there is established - * link between the two. + * Sets the port configured for power management purposes. */ -int tb_lc_configure_link(struct tb_switch *sw) +int tb_lc_configure_port(struct tb_port *port) { - struct tb_port *up, *down; - int ret; + return tb_lc_set_port_configured(port, true); +} + +/** + * tb_lc_unconfigure_port() - Let LC know about unconfigured port + * @port: Port that is set as configured + * + * Sets the port unconfigured for power management purposes. + */ +void tb_lc_unconfigure_port(struct tb_port *port) +{ + tb_lc_set_port_configured(port, false); +} - if (!tb_route(sw) || tb_switch_is_icm(sw)) +static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure) +{ + struct tb_switch *sw = port->sw; + u32 ctrl, lane; + int cap, ret; + + if (sw->generation < 2) return 0; - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), tb_to_switch(sw->dev.parent)); + cap = find_port_lc_cap(port); + if (cap < 0) + return cap; - /* Configure parent link toward this switch */ - ret = tb_lc_configure_lane(down, true); + ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); if (ret) return ret; - /* Configure upstream link from this switch to the parent */ - ret = tb_lc_configure_lane(up, true); + /* Resolve correct lane */ + if (port->port % 2) + lane = TB_LC_SX_CTRL_L1D; + else + lane = TB_LC_SX_CTRL_L2D; + + if (configure) + ctrl |= lane; + else + ctrl &= ~lane; + + return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); +} + +/** + * tb_lc_configure_xdomain() - Inform LC that the link is XDomain + * @port: Switch downstream port connected to another host + * + * Sets the lane configured for XDomain accordingly so that the LC knows + * about this. Returns %0 in success and negative errno in failure. + */ +int tb_lc_configure_xdomain(struct tb_port *port) +{ + return tb_lc_set_xdomain_configured(port, true); +} + +/** + * tb_lc_unconfigure_xdomain() - Unconfigure XDomain from port + * @port: Switch downstream port that was connected to another host + * + * Unsets the lane XDomain configuration. + */ +void tb_lc_unconfigure_xdomain(struct tb_port *port) +{ + tb_lc_set_xdomain_configured(port, false); +} + +static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, + unsigned int flags) +{ + u32 ctrl; + int ret; + + /* + * Enable wake on PCIe and USB4 (wake coming from another + * router). + */ + ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, + offset + TB_LC_SX_CTRL, 1); if (ret) - tb_lc_configure_lane(down, false); + return ret; + + ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WOP | + TB_LC_SX_CTRL_WOU4); + + if (flags & TB_WAKE_ON_CONNECT) + ctrl |= TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD; + if (flags & TB_WAKE_ON_USB4) + ctrl |= TB_LC_SX_CTRL_WOU4; + if (flags & TB_WAKE_ON_PCIE) + ctrl |= TB_LC_SX_CTRL_WOP; - return ret; + return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, offset + TB_LC_SX_CTRL, 1); } /** - * tb_lc_unconfigure_link() - Let LC know about unconfigured link - * @sw: Switch to unconfigure + * tb_lc_set_wake() - Enable/disable wake + * @sw: Switch whose wakes to configure + * @flags: Wakeup flags (%0 to disable) * - * Informs LC of both parent switch and @sw that the link between the - * two does not exist anymore. + * For each LC sets wake bits accordingly. */ -void tb_lc_unconfigure_link(struct tb_switch *sw) +int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags) { - struct tb_port *up, *down; + int start, size, nlc, ret, i; + u32 desc; - if (sw->is_unplugged || !tb_route(sw) || tb_switch_is_icm(sw)) - return; + if (sw->generation < 2) + return 0; - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), tb_to_switch(sw->dev.parent)); + if (!tb_route(sw)) + return 0; - tb_lc_configure_lane(up, false); - tb_lc_configure_lane(down, false); + ret = read_lc_desc(sw, &desc); + if (ret) + return ret; + + /* Figure out number of link controllers */ + nlc = desc & TB_LC_DESC_NLC_MASK; + start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT; + size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT; + + /* For each link controller set sleep bit */ + for (i = 0; i < nlc; i++) { + unsigned int offset = sw->cap_lc + start + i * size; + + ret = tb_lc_set_wake_one(sw, offset, flags); + if (ret) + return ret; + } + + return 0; } /** diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 5f7489fa1327..3f79baa54829 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/delay.h> #include <linux/property.h> +#include <linux/platform_data/x86/apple.h> #include "nhi.h" #include "nhi_regs.h" @@ -863,6 +864,22 @@ static int nhi_suspend_noirq(struct device *dev) return __nhi_suspend_noirq(dev, device_may_wakeup(dev)); } +static int nhi_freeze_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + + return tb_domain_freeze_noirq(tb); +} + +static int nhi_thaw_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + + return tb_domain_thaw_noirq(tb); +} + static bool nhi_wake_supported(struct pci_dev *pdev) { u8 val; @@ -1069,6 +1086,69 @@ static bool nhi_imr_valid(struct pci_dev *pdev) return true; } +/* + * During suspend the Thunderbolt controller is reset and all PCIe + * tunnels are lost. The NHI driver will try to reestablish all tunnels + * during resume. This adds device links between the tunneled PCIe + * downstream ports and the NHI so that the device core will make sure + * NHI is resumed first before the rest. + */ +static void tb_apple_add_links(struct tb_nhi *nhi) +{ + struct pci_dev *upstream, *pdev; + + if (!x86_apple_machine) + return; + + switch (nhi->pdev->device) { + case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE: + case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: + break; + default: + return; + } + + upstream = pci_upstream_bridge(nhi->pdev); + while (upstream) { + if (!pci_is_pcie(upstream)) + return; + if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM) + break; + upstream = pci_upstream_bridge(upstream); + } + + if (!upstream) + return; + + /* + * For each hotplug downstream port, create add device link + * back to NHI so that PCIe tunnels can be re-established after + * sleep. + */ + for_each_pci_bridge(pdev, upstream->subordinate) { + const struct device_link *link; + + if (!pci_is_pcie(pdev)) + continue; + if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM || + !pdev->is_hotplug_bridge) + continue; + + link = device_link_add(&pdev->dev, &nhi->pdev->dev, + DL_FLAG_AUTOREMOVE_SUPPLIER | + DL_FLAG_PM_RUNTIME); + if (link) { + dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_name(&pdev->dev)); + } else { + dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_name(&pdev->dev)); + } + } +} + static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct tb_nhi *nhi; @@ -1134,6 +1214,9 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) return res; } + tb_apple_add_links(nhi); + tb_acpi_add_links(nhi); + tb = icm_probe(nhi); if (!tb) tb = tb_probe(nhi); @@ -1157,6 +1240,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) } pci_set_drvdata(pdev, tb); + device_wakeup_enable(&pdev->dev); + pm_runtime_allow(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY); pm_runtime_use_autosuspend(&pdev->dev); @@ -1186,14 +1271,13 @@ static void nhi_remove(struct pci_dev *pdev) static const struct dev_pm_ops nhi_pm_ops = { .suspend_noirq = nhi_suspend_noirq, .resume_noirq = nhi_resume_noirq, - .freeze_noirq = nhi_suspend_noirq, /* + .freeze_noirq = nhi_freeze_noirq, /* * we just disable hotplug, the * pci-tunnels stay alive. */ - .thaw_noirq = nhi_resume_noirq, + .thaw_noirq = nhi_thaw_noirq, .restore_noirq = nhi_resume_noirq, .suspend = nhi_suspend, - .freeze = nhi_suspend, .poweroff_noirq = nhi_poweroff_noirq, .poweroff = nhi_suspend, .complete = nhi_complete, diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c index 6795851aac95..96da07e88c52 100644 --- a/drivers/thunderbolt/nhi_ops.c +++ b/drivers/thunderbolt/nhi_ops.c @@ -59,7 +59,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap); if (power) { - unsigned int retries = 10; + unsigned int retries = 350; u32 val; /* Wait until the firmware tells it is up and running */ @@ -67,7 +67,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) pci_read_config_dword(nhi->pdev, VS_CAP_9, &val); if (val & VS_CAP_9_FW_READY) return 0; - msleep(250); + usleep_range(3000, 3100); } while (--retries); return -ETIMEDOUT; @@ -97,7 +97,7 @@ static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) pci_read_config_dword(nhi->pdev, VS_CAP_18, &data); if (data & VS_CAP_18_DONE) goto clear; - msleep(100); + usleep_range(1000, 1100); } while (time_before(jiffies, end)); return -ETIMEDOUT; @@ -121,31 +121,38 @@ static void icl_nhi_set_ltr(struct tb_nhi *nhi) static int icl_nhi_suspend(struct tb_nhi *nhi) { + struct tb *tb = pci_get_drvdata(nhi->pdev); int ret; if (icl_nhi_is_device_connected(nhi)) return 0; - /* - * If there is no device connected we need to perform both: a - * handshake through LC mailbox and force power down before - * entering D3. - */ - icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); - ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); - if (ret) - return ret; + if (tb_switch_is_icm(tb->root_switch)) { + /* + * If there is no device connected we need to perform + * both: a handshake through LC mailbox and force power + * down before entering D3. + */ + icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); + ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); + if (ret) + return ret; + } return icl_nhi_force_power(nhi, false); } static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) { + struct tb *tb = pci_get_drvdata(nhi->pdev); enum icl_lc_mailbox_cmd cmd; if (!pm_suspend_via_firmware()) return icl_nhi_suspend(nhi); + if (!tb_switch_is_icm(tb->root_switch)) + return 0; + cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; icl_nhi_lc_mailbox_cmd(nhi, cmd); return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 7eac3e0f90a2..57e2978a3c21 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -27,7 +27,7 @@ static const struct tb_quirk tb_quirks[] = { * tb_check_quirks() - Check for quirks to apply * @sw: Thunderbolt switch * - * Apply any quirks for the Thunderbolt controller + * Apply any quirks for the Thunderbolt controller. */ void tb_check_quirks(struct tb_switch *sw) { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index a921de9ce7cb..c73bbfe69ba1 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -601,6 +601,13 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits) if (credits == 0 || port->sw->is_unplugged) return 0; + /* + * USB4 restricts programming NFC buffers to lane adapters only + * so skip other ports. + */ + if (tb_switch_is_usb4(port->sw) && !tb_port_is_null(port)) + return 0; + nfc_credits = port->config.nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK; nfc_credits += credits; @@ -666,6 +673,50 @@ int tb_port_unlock(struct tb_port *port) return 0; } +static int __tb_port_enable(struct tb_port *port, bool enable) +{ + int ret; + u32 phy; + + if (!tb_port_is_null(port)) + return -EINVAL; + + ret = tb_port_read(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + if (enable) + phy &= ~LANE_ADP_CS_1_LD; + else + phy |= LANE_ADP_CS_1_LD; + + return tb_port_write(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); +} + +/** + * tb_port_enable() - Enable lane adapter + * @port: Port to enable (can be %NULL) + * + * This is used for lane 0 and 1 adapters to enable it. + */ +int tb_port_enable(struct tb_port *port) +{ + return __tb_port_enable(port, true); +} + +/** + * tb_port_disable() - Disable lane adapter + * @port: Port to disable (can be %NULL) + * + * This is used for lane 0 and 1 adapters to disable it. + */ +int tb_port_disable(struct tb_port *port) +{ + return __tb_port_enable(port, false); +} + /** * tb_init_port() - initialize a port * @@ -739,7 +790,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, * NHI can use HopIDs 1-max for other adapters HopIDs 0-7 are * reserved. */ - if (port->config.type != TB_TYPE_NHI && min_hopid < TB_PATH_MIN_HOPID) + if (!tb_port_is_nhi(port) && min_hopid < TB_PATH_MIN_HOPID) min_hopid = TB_PATH_MIN_HOPID; if (max_hopid < 0 || max_hopid > port_max_hopid) @@ -1227,23 +1278,24 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw) /** * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET + * @sw: Switch to reset * * Return: Returns 0 on success or an error code on failure. */ -int tb_switch_reset(struct tb *tb, u64 route) +int tb_switch_reset(struct tb_switch *sw) { struct tb_cfg_result res; - struct tb_regs_switch_header header = { - header.route_hi = route >> 32, - header.route_lo = route, - header.enabled = true, - }; - tb_dbg(tb, "resetting switch at %llx\n", route); - res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route, - 0, 2, 2, 2); + + if (sw->generation > 1) + return 0; + + tb_sw_dbg(sw, "resetting switch\n"); + + res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2, + TB_CFG_SWITCH, 2, 2); if (res.err) return res.err; - res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT); + res = tb_cfg_reset(sw->tb->ctl, tb_route(sw), TB_CFG_DEFAULT_TIMEOUT); if (res.err > 0) return -EIO; return res.err; @@ -1261,7 +1313,7 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) u32 data; int res; - if (tb_switch_is_icm(sw)) + if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw)) return 0; sw->config.plug_events_delay = 0xff; @@ -1269,10 +1321,6 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) if (res) return res; - /* Plug events are always enabled in USB4 */ - if (tb_switch_is_usb4(sw)) - return 0; - res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1); if (res) return res; @@ -1649,7 +1697,7 @@ static struct attribute *switch_attrs[] = { static umode_t switch_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct tb_switch *sw = tb_to_switch(dev); if (attr == &dev_attr_device.attr) { @@ -1988,7 +2036,7 @@ int tb_switch_configure(struct tb_switch *sw) route = tb_route(sw); tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n", - sw->config.enabled ? "restoring " : "initializing", route, + sw->config.enabled ? "restoring" : "initializing", route, tb_route_length(route), sw->config.upstream_port_number); sw->config.enabled = 1; @@ -2008,10 +2056,6 @@ int tb_switch_configure(struct tb_switch *sw) return ret; ret = usb4_switch_setup(sw); - if (ret) - return ret; - - ret = usb4_switch_configure_link(sw); } else { if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) tb_sw_warn(sw, "unknown switch vendor id %#x\n", @@ -2025,10 +2069,6 @@ int tb_switch_configure(struct tb_switch *sw) /* Enumerate the switch */ ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH, ROUTER_CS_1, 3); - if (ret) - return ret; - - ret = tb_lc_configure_link(sw); } if (ret) return ret; @@ -2312,6 +2352,69 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw) } /** + * tb_switch_configure_link() - Set link configured + * @sw: Switch whose link is configured + * + * Sets the link upstream from @sw configured (from both ends) so that + * it will not be disconnected when the domain exits sleep. Can be + * called for any switch. + * + * It is recommended that this is called after lane bonding is enabled. + * + * Returns %0 on success and negative errno in case of error. + */ +int tb_switch_configure_link(struct tb_switch *sw) +{ + struct tb_port *up, *down; + int ret; + + if (!tb_route(sw) || tb_switch_is_icm(sw)) + return 0; + + up = tb_upstream_port(sw); + if (tb_switch_is_usb4(up->sw)) + ret = usb4_port_configure(up); + else + ret = tb_lc_configure_port(up); + if (ret) + return ret; + + down = up->remote; + if (tb_switch_is_usb4(down->sw)) + return usb4_port_configure(down); + return tb_lc_configure_port(down); +} + +/** + * tb_switch_unconfigure_link() - Unconfigure link + * @sw: Switch whose link is unconfigured + * + * Sets the link unconfigured so the @sw will be disconnected if the + * domain exists sleep. + */ +void tb_switch_unconfigure_link(struct tb_switch *sw) +{ + struct tb_port *up, *down; + + if (sw->is_unplugged) + return; + if (!tb_route(sw) || tb_switch_is_icm(sw)) + return; + + up = tb_upstream_port(sw); + if (tb_switch_is_usb4(up->sw)) + usb4_port_unconfigure(up); + else + tb_lc_unconfigure_port(up); + + down = up->remote; + if (tb_switch_is_usb4(down->sw)) + usb4_port_unconfigure(down); + else + tb_lc_unconfigure_port(down); +} + +/** * tb_switch_add() - Add a switch to the domain * @sw: Switch to add * @@ -2399,6 +2502,13 @@ int tb_switch_add(struct tb_switch *sw) return ret; } + /* + * Thunderbolt routers do not generate wakeups themselves but + * they forward wakeups from tunneled protocols, so enable it + * here. + */ + device_init_wakeup(&sw->dev, true); + pm_runtime_set_active(&sw->dev); if (sw->rpm) { pm_runtime_set_autosuspend_delay(&sw->dev, TB_AUTOSUSPEND_DELAY); @@ -2408,6 +2518,7 @@ int tb_switch_add(struct tb_switch *sw) pm_request_autosuspend(&sw->dev); } + tb_switch_debugfs_init(sw); return 0; } @@ -2423,6 +2534,8 @@ void tb_switch_remove(struct tb_switch *sw) { struct tb_port *port; + tb_switch_debugfs_remove(sw); + if (sw->rpm) { pm_runtime_get_sync(&sw->dev); pm_runtime_disable(&sw->dev); @@ -2445,11 +2558,6 @@ void tb_switch_remove(struct tb_switch *sw) if (!sw->is_unplugged) tb_plug_events_active(sw, false); - if (tb_switch_is_usb4(sw)) - usb4_switch_unconfigure_link(sw); - else - tb_lc_unconfigure_link(sw); - tb_switch_nvm_remove(sw); if (tb_route(sw)) @@ -2481,6 +2589,18 @@ void tb_sw_set_unplugged(struct tb_switch *sw) } } +static int tb_switch_set_wake(struct tb_switch *sw, unsigned int flags) +{ + if (flags) + tb_sw_dbg(sw, "enabling wakeup: %#x\n", flags); + else + tb_sw_dbg(sw, "disabling wakeup\n"); + + if (tb_switch_is_usb4(sw)) + return usb4_switch_set_wake(sw, flags); + return tb_lc_set_wake(sw, flags); +} + int tb_switch_resume(struct tb_switch *sw) { struct tb_port *port; @@ -2526,6 +2646,13 @@ int tb_switch_resume(struct tb_switch *sw) if (err) return err; + /* Disable wakes */ + tb_switch_set_wake(sw, 0); + + err = tb_switch_tmu_init(sw); + if (err) + return err; + /* check for surviving downstream switches */ tb_switch_for_each_port(sw, port) { if (!tb_port_has_remote(port) && !port->xdomain) @@ -2555,20 +2682,43 @@ int tb_switch_resume(struct tb_switch *sw) return 0; } -void tb_switch_suspend(struct tb_switch *sw) +/** + * tb_switch_suspend() - Put a switch to sleep + * @sw: Switch to suspend + * @runtime: Is this runtime suspend or system sleep + * + * Suspends router and all its children. Enables wakes according to + * value of @runtime and then sets sleep bit for the router. If @sw is + * host router the domain is ready to go to sleep once this function + * returns. + */ +void tb_switch_suspend(struct tb_switch *sw, bool runtime) { + unsigned int flags = 0; struct tb_port *port; int err; + tb_sw_dbg(sw, "suspending switch\n"); + err = tb_plug_events_active(sw, false); if (err) return; tb_switch_for_each_port(sw, port) { if (tb_port_has_remote(port)) - tb_switch_suspend(port->remote->sw); + tb_switch_suspend(port->remote->sw, runtime); } + if (runtime) { + /* Trigger wake when something is plugged in/out */ + flags |= TB_WAKE_ON_CONNECT | TB_WAKE_ON_DISCONNECT; + flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE; + } else if (device_may_wakeup(&sw->dev)) { + flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE; + } + + tb_switch_set_wake(sw, flags); + if (tb_switch_is_usb4(sw)) usb4_switch_set_sleep(sw); else diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index f507815040eb..214fbc92c1b7 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -9,6 +9,7 @@ #include <linux/slab.h> #include <linux/errno.h> #include <linux/delay.h> +#include <linux/pm_runtime.h> #include "tb.h" #include "tb_regs.h" @@ -22,13 +23,21 @@ * events and exit if this is not set (it needs to * acquire the lock one more time). Used to drain wq * after cfg has been paused. + * @remove_work: Work used to remove any unplugged routers after + * runtime resume */ struct tb_cm { struct list_head tunnel_list; struct list_head dp_resources; bool hotplug_active; + struct delayed_work remove_work; }; +static inline struct tb *tcm_to_tb(struct tb_cm *tcm) +{ + return ((void *)tcm - sizeof(struct tb)); +} + struct tb_hotplug_event { struct work_struct work; struct tb *tb; @@ -140,6 +149,29 @@ static void tb_discover_tunnels(struct tb_switch *sw) } } +static int tb_port_configure_xdomain(struct tb_port *port) +{ + /* + * XDomain paths currently only support single lane so we must + * disable the other lane according to USB4 spec. + */ + tb_port_disable(port->dual_link_port); + + if (tb_switch_is_usb4(port->sw)) + return usb4_port_configure_xdomain(port); + return tb_lc_configure_xdomain(port); +} + +static void tb_port_unconfigure_xdomain(struct tb_port *port) +{ + if (tb_switch_is_usb4(port->sw)) + usb4_port_unconfigure_xdomain(port); + else + tb_lc_unconfigure_xdomain(port); + + tb_port_enable(port->dual_link_port); +} + static void tb_scan_xdomain(struct tb_port *port) { struct tb_switch *sw = port->sw; @@ -158,6 +190,7 @@ static void tb_scan_xdomain(struct tb_port *port) NULL); if (xd) { tb_port_at(route, sw)->xdomain = xd; + tb_port_configure_xdomain(port); tb_xdomain_add(xd); } } @@ -502,8 +535,13 @@ static void tb_scan_switch(struct tb_switch *sw) { struct tb_port *port; + pm_runtime_get_sync(&sw->dev); + tb_switch_for_each_port(sw, port) tb_scan_port(port); + + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); } /** @@ -566,6 +604,7 @@ static void tb_scan_port(struct tb_port *port) */ if (port->xdomain) { tb_xdomain_remove(port->xdomain); + tb_port_unconfigure_xdomain(port); port->xdomain = NULL; } @@ -577,6 +616,12 @@ static void tb_scan_port(struct tb_port *port) if (!tcm->hotplug_active) dev_set_uevent_suppress(&sw->dev, true); + /* + * At the moment Thunderbolt 2 and beyond (devices with LC) we + * can support runtime PM. + */ + sw->rpm = sw->generation > 1; + if (tb_switch_add(sw)) { tb_switch_put(sw); return; @@ -592,8 +637,9 @@ static void tb_scan_port(struct tb_port *port) } /* Enable lane bonding if supported */ - if (tb_switch_lane_bonding_enable(sw)) - tb_sw_warn(sw, "failed to enable lane bonding\n"); + tb_switch_lane_bonding_enable(sw); + /* Set the link configured */ + tb_switch_configure_link(sw); if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to enable TMU\n"); @@ -636,6 +682,11 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel) * deallocated properly. */ tb_switch_dealloc_dp_resource(src_port->sw, src_port); + /* Now we can allow the domain to runtime suspend again */ + pm_runtime_mark_last_busy(&dst_port->sw->dev); + pm_runtime_put_autosuspend(&dst_port->sw->dev); + pm_runtime_mark_last_busy(&src_port->sw->dev); + pm_runtime_put_autosuspend(&src_port->sw->dev); fallthrough; case TB_TUNNEL_USB3: @@ -682,6 +733,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw) if (port->remote->sw->is_unplugged) { tb_retimer_remove_all(port); tb_remove_dp_resources(port->remote->sw); + tb_switch_unconfigure_link(port->remote->sw); tb_switch_lane_bonding_disable(port->remote->sw); tb_switch_remove(port->remote->sw); port->remote = NULL; @@ -821,9 +873,20 @@ static void tb_tunnel_dp(struct tb *tb) return; } + /* + * DP stream needs the domain to be active so runtime resume + * both ends of the tunnel. + * + * This should bring the routers in the middle active as well + * and keeps the domain from runtime suspending while the DP + * tunnel is active. + */ + pm_runtime_get_sync(&in->sw->dev); + pm_runtime_get_sync(&out->sw->dev); + if (tb_switch_alloc_dp_resource(in->sw, in)) { tb_port_dbg(in, "no resource available for DP IN, not tunneling\n"); - return; + goto err_rpm_put; } /* Make all unused USB3 bandwidth available for the new DP tunnel */ @@ -862,6 +925,11 @@ err_reclaim: tb_reclaim_usb3_bandwidth(tb, in, out); err_dealloc_dp: tb_switch_dealloc_dp_resource(in->sw, in); +err_rpm_put: + pm_runtime_mark_last_busy(&out->sw->dev); + pm_runtime_put_autosuspend(&out->sw->dev); + pm_runtime_mark_last_busy(&in->sw->dev); + pm_runtime_put_autosuspend(&in->sw->dev); } static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port) @@ -911,6 +979,29 @@ static void tb_dp_resource_available(struct tb *tb, struct tb_port *port) tb_tunnel_dp(tb); } +static void tb_disconnect_and_release_dp(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel, *n; + + /* + * Tear down all DP tunnels and release their resources. They + * will be re-established after resume based on plug events. + */ + list_for_each_entry_safe_reverse(tunnel, n, &tcm->tunnel_list, list) { + if (tb_tunnel_is_dp(tunnel)) + tb_deactivate_and_free_tunnel(tunnel); + } + + while (!list_empty(&tcm->dp_resources)) { + struct tb_port *port; + + port = list_first_entry(&tcm->dp_resources, + struct tb_port, list); + list_del_init(&port->list); + } +} + static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) { struct tb_port *up, *down, *port; @@ -1022,6 +1113,10 @@ static void tb_handle_hotplug(struct work_struct *work) struct tb_cm *tcm = tb_priv(tb); struct tb_switch *sw; struct tb_port *port; + + /* Bring the domain back from sleep if it was suspended */ + pm_runtime_get_sync(&tb->dev); + mutex_lock(&tb->lock); if (!tcm->hotplug_active) goto out; /* during init, suspend or shutdown */ @@ -1045,6 +1140,9 @@ static void tb_handle_hotplug(struct work_struct *work) ev->route, ev->port, ev->unplug); goto put_sw; } + + pm_runtime_get_sync(&sw->dev); + if (ev->unplug) { tb_retimer_remove_all(port); @@ -1054,6 +1152,7 @@ static void tb_handle_hotplug(struct work_struct *work) tb_free_invalid_tunnels(tb); tb_remove_dp_resources(port->remote->sw); tb_switch_tmu_disable(port->remote->sw); + tb_switch_unconfigure_link(port->remote->sw); tb_switch_lane_bonding_disable(port->remote->sw); tb_switch_remove(port->remote->sw); port->remote = NULL; @@ -1077,6 +1176,7 @@ static void tb_handle_hotplug(struct work_struct *work) port->xdomain = NULL; __tb_disconnect_xdomain_paths(tb, xd); tb_xdomain_put(xd); + tb_port_unconfigure_xdomain(port); } else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) { tb_dp_resource_unavailable(tb, port); } else { @@ -1096,10 +1196,17 @@ static void tb_handle_hotplug(struct work_struct *work) } } + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); + put_sw: tb_switch_put(sw); out: mutex_unlock(&tb->lock); + + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_put_autosuspend(&tb->dev); + kfree(ev); } @@ -1135,6 +1242,7 @@ static void tb_stop(struct tb *tb) struct tb_tunnel *tunnel; struct tb_tunnel *n; + cancel_delayed_work(&tcm->remove_work); /* tunnels are only present after everything has been initialized */ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { /* @@ -1186,6 +1294,8 @@ static int tb_start(struct tb *tb) * root switch. */ tb->root_switch->no_nvm_upgrade = true; + /* All USB4 routers support runtime PM */ + tb->root_switch->rpm = tb_switch_is_usb4(tb->root_switch); ret = tb_switch_configure(tb->root_switch); if (ret) { @@ -1227,7 +1337,8 @@ static int tb_suspend_noirq(struct tb *tb) struct tb_cm *tcm = tb_priv(tb); tb_dbg(tb, "suspending...\n"); - tb_switch_suspend(tb->root_switch); + tb_disconnect_and_release_dp(tb); + tb_switch_suspend(tb->root_switch, false); tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ tb_dbg(tb, "suspend finished\n"); @@ -1238,17 +1349,25 @@ static void tb_restore_children(struct tb_switch *sw) { struct tb_port *port; + /* No need to restore if the router is already unplugged */ + if (sw->is_unplugged) + return; + if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to restore TMU configuration\n"); tb_switch_for_each_port(sw, port) { - if (!tb_port_has_remote(port)) + if (!tb_port_has_remote(port) && !port->xdomain) continue; - if (tb_switch_lane_bonding_enable(port->remote->sw)) - dev_warn(&sw->dev, "failed to restore lane bonding\n"); + if (port->remote) { + tb_switch_lane_bonding_enable(port->remote->sw); + tb_switch_configure_link(port->remote->sw); - tb_restore_children(port->remote->sw); + tb_restore_children(port->remote->sw); + } else if (port->xdomain) { + tb_port_configure_xdomain(port); + } } } @@ -1260,7 +1379,7 @@ static int tb_resume_noirq(struct tb *tb) tb_dbg(tb, "resuming...\n"); /* remove any pci devices the firmware might have setup */ - tb_switch_reset(tb, 0); + tb_switch_reset(tb->root_switch); tb_switch_resume(tb->root_switch); tb_free_invalid_tunnels(tb); @@ -1294,6 +1413,7 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw) if (port->xdomain && port->xdomain->is_unplugged) { tb_retimer_remove_all(port); tb_xdomain_remove(port->xdomain); + tb_port_unconfigure_xdomain(port); port->xdomain = NULL; ret++; } else if (port->remote) { @@ -1304,6 +1424,22 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw) return ret; } +static int tb_freeze_noirq(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + tcm->hotplug_active = false; + return 0; +} + +static int tb_thaw_noirq(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + tcm->hotplug_active = true; + return 0; +} + static void tb_complete(struct tb *tb) { /* @@ -1317,12 +1453,64 @@ static void tb_complete(struct tb *tb) mutex_unlock(&tb->lock); } +static int tb_runtime_suspend(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + mutex_lock(&tb->lock); + tb_switch_suspend(tb->root_switch, true); + tcm->hotplug_active = false; + mutex_unlock(&tb->lock); + + return 0; +} + +static void tb_remove_work(struct work_struct *work) +{ + struct tb_cm *tcm = container_of(work, struct tb_cm, remove_work.work); + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); + if (tb->root_switch) { + tb_free_unplugged_children(tb->root_switch); + tb_free_unplugged_xdomains(tb->root_switch); + } + mutex_unlock(&tb->lock); +} + +static int tb_runtime_resume(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel, *n; + + mutex_lock(&tb->lock); + tb_switch_resume(tb->root_switch); + tb_free_invalid_tunnels(tb); + tb_restore_children(tb->root_switch); + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) + tb_tunnel_restart(tunnel); + tcm->hotplug_active = true; + mutex_unlock(&tb->lock); + + /* + * Schedule cleanup of any unplugged devices. Run this in a + * separate thread to avoid possible deadlock if the device + * removal runtime resumes the unplugged device. + */ + queue_delayed_work(tb->wq, &tcm->remove_work, msecs_to_jiffies(50)); + return 0; +} + static const struct tb_cm_ops tb_cm_ops = { .start = tb_start, .stop = tb_stop, .suspend_noirq = tb_suspend_noirq, .resume_noirq = tb_resume_noirq, + .freeze_noirq = tb_freeze_noirq, + .thaw_noirq = tb_thaw_noirq, .complete = tb_complete, + .runtime_suspend = tb_runtime_suspend, + .runtime_resume = tb_runtime_resume, .handle_event = tb_handle_event, .approve_switch = tb_tunnel_pci, .approve_xdomain_paths = tb_approve_xdomain_paths, @@ -1344,6 +1532,7 @@ struct tb *tb_probe(struct tb_nhi *nhi) tcm = tb_priv(tb); INIT_LIST_HEAD(&tcm->tunnel_list); INIT_LIST_HEAD(&tcm->dp_resources); + INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work); return tb; } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 3c620a9203c5..a9995e21b916 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -125,6 +125,7 @@ struct tb_switch_tmu { * @rpm: The switch supports runtime PM * @authorized: Whether the switch is authorized by user or policy * @security_level: Switch supported security level + * @debugfs_dir: Pointer to the debugfs structure * @key: Contains the key used to challenge the device or %NULL if not * supported. Size of the key is %TB_SWITCH_KEY_SIZE. * @connection_id: Connection ID used with ICM messaging @@ -166,6 +167,7 @@ struct tb_switch { bool rpm; unsigned int authorized; enum tb_security_level security_level; + struct dentry *debugfs_dir; u8 *key; u8 connection_id; u8 connection_key; @@ -333,6 +335,13 @@ struct tb_path { */ #define TB_PATH_MAX_HOPS (7 * 2) +/* Possible wake types */ +#define TB_WAKE_ON_CONNECT BIT(0) +#define TB_WAKE_ON_DISCONNECT BIT(1) +#define TB_WAKE_ON_USB4 BIT(2) +#define TB_WAKE_ON_USB3 BIT(3) +#define TB_WAKE_ON_PCIE BIT(4) + /** * struct tb_cm_ops - Connection manager specific operations vector * @driver_ready: Called right after control channel is started. Used by @@ -342,6 +351,8 @@ struct tb_path { * @suspend_noirq: Connection manager specific suspend_noirq * @resume_noirq: Connection manager specific resume_noirq * @suspend: Connection manager specific suspend + * @freeze_noirq: Connection manager specific freeze_noirq + * @thaw_noirq: Connection manager specific thaw_noirq * @complete: Connection manager specific complete * @runtime_suspend: Connection manager specific runtime_suspend * @runtime_resume: Connection manager specific runtime_resume @@ -364,6 +375,8 @@ struct tb_cm_ops { int (*suspend_noirq)(struct tb *tb); int (*resume_noirq)(struct tb *tb); int (*suspend)(struct tb *tb); + int (*freeze_noirq)(struct tb *tb); + int (*thaw_noirq)(struct tb *tb); void (*complete)(struct tb *tb); int (*runtime_suspend)(struct tb *tb); int (*runtime_resume)(struct tb *tb); @@ -457,6 +470,11 @@ static inline bool tb_port_is_null(const struct tb_port *port) return port && port->port && port->config.type == TB_TYPE_PORT; } +static inline bool tb_port_is_nhi(const struct tb_port *port) +{ + return port && port->config.type == TB_TYPE_NHI; +} + static inline bool tb_port_is_pcie_down(const struct tb_port *port) { return port && port->config.type == TB_TYPE_PCIE_DOWN; @@ -593,6 +611,8 @@ void tb_domain_remove(struct tb *tb); int tb_domain_suspend_noirq(struct tb *tb); int tb_domain_resume_noirq(struct tb *tb); int tb_domain_suspend(struct tb *tb); +int tb_domain_freeze_noirq(struct tb *tb); +int tb_domain_thaw_noirq(struct tb *tb); void tb_domain_complete(struct tb *tb); int tb_domain_runtime_suspend(struct tb *tb); int tb_domain_runtime_resume(struct tb *tb); @@ -632,9 +652,9 @@ struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb, int tb_switch_configure(struct tb_switch *sw); int tb_switch_add(struct tb_switch *sw); void tb_switch_remove(struct tb_switch *sw); -void tb_switch_suspend(struct tb_switch *sw); +void tb_switch_suspend(struct tb_switch *sw, bool runtime); int tb_switch_resume(struct tb_switch *sw); -int tb_switch_reset(struct tb *tb, u64 route); +int tb_switch_reset(struct tb_switch *sw); void tb_sw_set_unplugged(struct tb_switch *sw); struct tb_port *tb_switch_find_port(struct tb_switch *sw, enum tb_port_type type); @@ -685,59 +705,89 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw) static inline bool tb_switch_is_light_ridge(const struct tb_switch *sw) { - return sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE; + return sw->config.vendor_id == PCI_VENDOR_ID_INTEL && + sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE; } static inline bool tb_switch_is_eagle_ridge(const struct tb_switch *sw) { - return sw->config.device_id == PCI_DEVICE_ID_INTEL_EAGLE_RIDGE; + return sw->config.vendor_id == PCI_VENDOR_ID_INTEL && + sw->config.device_id == PCI_DEVICE_ID_INTEL_EAGLE_RIDGE; } static inline bool tb_switch_is_cactus_ridge(const struct tb_switch *sw) { - switch (sw->config.device_id) { - case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C: - case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: - return true; - default: - return false; + if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) { + switch (sw->config.device_id) { + case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C: + case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: + return true; + } } + return false; } static inline bool tb_switch_is_falcon_ridge(const struct tb_switch *sw) { - switch (sw->config.device_id) { - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE: - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE: - return true; - default: - return false; + if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) { + switch (sw->config.device_id) { + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE: + return true; + } } + return false; } static inline bool tb_switch_is_alpine_ridge(const struct tb_switch *sw) { - switch (sw->config.device_id) { - case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE: - case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE: - case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE: - case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE: - return true; - default: - return false; + if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) { + switch (sw->config.device_id) { + case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE: + case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE: + case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE: + case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE: + return true; + } } + return false; } static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw) { - switch (sw->config.device_id) { - case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE: - case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE: - case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE: - return true; - default: - return false; + if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) { + switch (sw->config.device_id) { + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE: + case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE: + return true; + } + } + return false; +} + +static inline bool tb_switch_is_ice_lake(const struct tb_switch *sw) +{ + if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) { + switch (sw->config.device_id) { + case PCI_DEVICE_ID_INTEL_ICL_NHI0: + case PCI_DEVICE_ID_INTEL_ICL_NHI1: + return true; + } + } + return false; +} + +static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw) +{ + if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) { + switch (sw->config.device_id) { + case PCI_DEVICE_ID_INTEL_TGL_NHI0: + case PCI_DEVICE_ID_INTEL_TGL_NHI1: + return true; + } } + return false; } /** @@ -767,6 +817,8 @@ static inline bool tb_switch_is_icm(const struct tb_switch *sw) int tb_switch_lane_bonding_enable(struct tb_switch *sw); void tb_switch_lane_bonding_disable(struct tb_switch *sw); +int tb_switch_configure_link(struct tb_switch *sw); +void tb_switch_unconfigure_link(struct tb_switch *sw); bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in); int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in); @@ -788,6 +840,8 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits); int tb_port_set_initial_credits(struct tb_port *port, u32 credits); int tb_port_clear_counter(struct tb_port *port, int counter); int tb_port_unlock(struct tb_port *port); +int tb_port_enable(struct tb_port *port); +int tb_port_disable(struct tb_port *port); int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); void tb_port_release_in_hopid(struct tb_port *port, int hopid); int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); @@ -811,7 +865,9 @@ int tb_port_get_link_speed(struct tb_port *port); int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec); int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap); +int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset); int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap); +int tb_port_next_cap(struct tb_port *port, unsigned int offset); bool tb_port_is_enabled(struct tb_port *port); bool tb_usb3_port_is_enabled(struct tb_port *port); @@ -844,8 +900,11 @@ int tb_drom_read(struct tb_switch *sw); int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid); int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid); -int tb_lc_configure_link(struct tb_switch *sw); -void tb_lc_unconfigure_link(struct tb_switch *sw); +int tb_lc_configure_port(struct tb_port *port); +void tb_lc_unconfigure_port(struct tb_port *port); +int tb_lc_configure_xdomain(struct tb_port *port); +void tb_lc_unconfigure_xdomain(struct tb_port *port); +int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags); int tb_lc_set_sleep(struct tb_switch *sw); bool tb_lc_lane_bonding_possible(struct tb_switch *sw); bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in); @@ -900,9 +959,8 @@ int usb4_switch_setup(struct tb_switch *sw); int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid); int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size); -int usb4_switch_configure_link(struct tb_switch *sw); -void usb4_switch_unconfigure_link(struct tb_switch *sw); bool usb4_switch_lane_bonding_possible(struct tb_switch *sw); +int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags); 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, @@ -919,6 +977,10 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port); int usb4_port_unlock(struct tb_port *port); +int usb4_port_configure(struct tb_port *port); +void usb4_port_unconfigure(struct tb_port *port); +int usb4_port_configure_xdomain(struct tb_port *port); +void usb4_port_unconfigure_xdomain(struct tb_port *port); int usb4_port_enumerate_retimers(struct tb_port *port); int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf, @@ -945,9 +1007,35 @@ 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); -/* keep link controller awake during update */ +/* Keep link controller awake during update */ #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) void tb_check_quirks(struct tb_switch *sw); +#ifdef CONFIG_ACPI +void tb_acpi_add_links(struct tb_nhi *nhi); +#else +static inline void tb_acpi_add_links(struct tb_nhi *nhi) { } +#endif + +#ifdef CONFIG_DEBUG_FS +void tb_debugfs_init(void); +void tb_debugfs_exit(void); +void tb_switch_debugfs_init(struct tb_switch *sw); +void tb_switch_debugfs_remove(struct tb_switch *sw); +#else +static inline void tb_debugfs_init(void) { } +static inline void tb_debugfs_exit(void) { } +static inline void tb_switch_debugfs_init(struct tb_switch *sw) { } +static inline void tb_switch_debugfs_remove(struct tb_switch *sw) { } +#endif + +#ifdef CONFIG_USB4_KUNIT_TEST +int tb_test_init(void); +void tb_test_exit(void); +#else +static inline int tb_test_init(void) { return 0; } +static inline void tb_test_exit(void) { } +#endif + #endif diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index fc208c567953..0e01dbc63e72 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -28,6 +28,7 @@ enum tb_cfg_error { TB_CFG_ERROR_LOOP = 8, TB_CFG_ERROR_HEC_ERROR_DETECTED = 12, TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13, + TB_CFG_ERROR_LOCK = 15, }; /* common header */ diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index fd4fc144d17f..e7d9529822fa 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -39,6 +39,7 @@ enum tb_switch_vse_cap { enum tb_port_cap { TB_PORT_CAP_PHY = 0x01, + TB_PORT_CAP_POWER = 0x02, TB_PORT_CAP_TIME1 = 0x03, TB_PORT_CAP_ADAP = 0x04, TB_PORT_CAP_VSE = 0x05, @@ -93,6 +94,20 @@ struct tb_cap_extended_long { u16 length; } __packed; +/** + * struct tb_cap_any - Structure capable of hold every capability + * @basic: Basic capability + * @extended_short: Vendor specific capability + * @extended_long: Vendor specific extended capability + */ +struct tb_cap_any { + union { + struct tb_cap_basic basic; + struct tb_cap_extended_short extended_short; + struct tb_cap_extended_long extended_long; + }; +} __packed; + /* capabilities */ struct tb_cap_link_controller { @@ -178,6 +193,8 @@ struct tb_regs_switch_header { #define ROUTER_CS_4 0x04 #define ROUTER_CS_5 0x05 #define ROUTER_CS_5_SLP BIT(0) +#define ROUTER_CS_5_WOP BIT(1) +#define ROUTER_CS_5_WOU BIT(2) #define ROUTER_CS_5_C3S BIT(23) #define ROUTER_CS_5_PTO BIT(24) #define ROUTER_CS_5_UTO BIT(25) @@ -186,6 +203,8 @@ struct tb_regs_switch_header { #define ROUTER_CS_6 0x06 #define ROUTER_CS_6_SLPR BIT(0) #define ROUTER_CS_6_TNS BIT(1) +#define ROUTER_CS_6_WOPS BIT(2) +#define ROUTER_CS_6_WOUS BIT(3) #define ROUTER_CS_6_HCI BIT(18) #define ROUTER_CS_6_CR BIT(25) #define ROUTER_CS_7 0x07 @@ -234,7 +253,8 @@ struct tb_regs_port_header { /* DWORD 1 */ u32 first_cap_offset:8; u32 max_counters:11; - u32 __unknown1:5; + u32 counters_support:1; + u32 __unknown1:4; u32 revision:8; /* DWORD 2 */ enum tb_port_type type:24; @@ -279,6 +299,7 @@ struct tb_regs_port_header { #define LANE_ADP_CS_1_TARGET_WIDTH_SHIFT 4 #define LANE_ADP_CS_1_TARGET_WIDTH_SINGLE 0x1 #define LANE_ADP_CS_1_TARGET_WIDTH_DUAL 0x3 +#define LANE_ADP_CS_1_LD BIT(14) #define LANE_ADP_CS_1_LB BIT(15) #define LANE_ADP_CS_1_CURRENT_SPEED_MASK GENMASK(19, 16) #define LANE_ADP_CS_1_CURRENT_SPEED_SHIFT 16 @@ -301,8 +322,13 @@ struct tb_regs_port_header { #define PORT_CS_18 0x12 #define PORT_CS_18_BE BIT(8) #define PORT_CS_18_TCM BIT(9) +#define PORT_CS_18_WOU4S BIT(18) #define PORT_CS_19 0x13 #define PORT_CS_19_PC BIT(3) +#define PORT_CS_19_PID BIT(4) +#define PORT_CS_19_WOC BIT(16) +#define PORT_CS_19_WOD BIT(17) +#define PORT_CS_19_WOU4 BIT(18) /* Display Port adapter registers */ #define ADP_DP_CS_0 0x00 @@ -416,8 +442,14 @@ struct tb_regs_hop { #define TB_LC_PORT_ATTR_BE BIT(12) #define TB_LC_SX_CTRL 0x96 +#define TB_LC_SX_CTRL_WOC BIT(1) +#define TB_LC_SX_CTRL_WOD BIT(2) +#define TB_LC_SX_CTRL_WOU4 BIT(5) +#define TB_LC_SX_CTRL_WOP BIT(6) #define TB_LC_SX_CTRL_L1C BIT(16) +#define TB_LC_SX_CTRL_L1D BIT(17) #define TB_LC_SX_CTRL_L2C BIT(20) +#define TB_LC_SX_CTRL_L2D BIT(21) #define TB_LC_SX_CTRL_UPSTREAM BIT(30) #define TB_LC_SX_CTRL_SLP BIT(31) diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index a4d78811f7e2..464c2d37b992 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -1623,4 +1623,15 @@ static struct kunit_suite tb_test_suite = { .name = "thunderbolt", .test_cases = tb_test_cases, }; -kunit_test_suite(tb_test_suite); + +static struct kunit_suite *tb_test_suites[] = { &tb_test_suite, NULL }; + +int tb_test_init(void) +{ + return __kunit_test_suites_init(tb_test_suites); +} + +void tb_test_exit(void) +{ + return __kunit_test_suites_exit(tb_test_suites); +} diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 2b8355e6b65f..40f13579a3fe 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -196,6 +196,46 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status) return 0; } +static void usb4_switch_check_wakes(struct tb_switch *sw) +{ + struct tb_port *port; + bool wakeup = false; + u32 val; + + if (!device_may_wakeup(&sw->dev)) + return; + + if (tb_route(sw)) { + if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1)) + return; + + tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n", + (val & ROUTER_CS_6_WOPS) ? "yes" : "no", + (val & ROUTER_CS_6_WOUS) ? "yes" : "no"); + + wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS); + } + + /* Check for any connected downstream ports for USB4 wake */ + tb_switch_for_each_port(sw, port) { + if (!tb_port_has_remote(port)) + continue; + + if (tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_18, 1)) + break; + + tb_port_dbg(port, "USB4 wake: %s\n", + (val & PORT_CS_18_WOU4S) ? "yes" : "no"); + + if (val & PORT_CS_18_WOU4S) + wakeup = true; + } + + if (wakeup) + pm_wakeup_event(&sw->dev, 0); +} + static bool link_is_usb4(struct tb_port *port) { u32 val; @@ -229,6 +269,8 @@ int usb4_switch_setup(struct tb_switch *sw) u32 val = 0; int ret; + usb4_switch_check_wakes(sw); + if (!tb_route(sw)) return 0; @@ -338,87 +380,99 @@ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, usb4_switch_drom_read_block, sw); } -static int usb4_set_port_configured(struct tb_port *port, bool configured) +/** + * usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding + * @sw: USB4 router + * + * Checks whether conditions are met so that lane bonding can be + * established with the upstream router. Call only for device routers. + */ +bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) { + struct tb_port *up; int ret; u32 val; - ret = tb_port_read(port, &val, TB_CFG_PORT, - port->cap_usb4 + PORT_CS_19, 1); + up = tb_upstream_port(sw); + ret = tb_port_read(up, &val, TB_CFG_PORT, up->cap_usb4 + PORT_CS_18, 1); if (ret) - return ret; - - if (configured) - val |= PORT_CS_19_PC; - else - val &= ~PORT_CS_19_PC; + return false; - return tb_port_write(port, &val, TB_CFG_PORT, - port->cap_usb4 + PORT_CS_19, 1); + return !!(val & PORT_CS_18_BE); } /** - * usb4_switch_configure_link() - Set upstream USB4 link configured + * usb4_switch_set_wake() - Enabled/disable wake * @sw: USB4 router + * @flags: Wakeup flags (%0 to disable) * - * Sets the upstream USB4 link to be configured for power management - * purposes. + * Enables/disables router to wake up from sleep. */ -int usb4_switch_configure_link(struct tb_switch *sw) +int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags) { - struct tb_port *up; + struct tb_port *port; + u64 route = tb_route(sw); + u32 val; + int ret; - if (!tb_route(sw)) - return 0; + /* + * Enable wakes coming from all USB4 downstream ports (from + * child routers). For device routers do this also for the + * upstream USB4 port. + */ + tb_switch_for_each_port(sw, port) { + if (!route && tb_is_upstream_port(port)) + continue; - up = tb_upstream_port(sw); - return usb4_set_port_configured(up, true); -} + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1); + if (ret) + return ret; -/** - * usb4_switch_unconfigure_link() - Un-set upstream USB4 link configuration - * @sw: USB4 router - * - * Reverse of usb4_switch_configure_link(). - */ -void usb4_switch_unconfigure_link(struct tb_switch *sw) -{ - struct tb_port *up; + val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4); - if (sw->is_unplugged || !tb_route(sw)) - return; + if (flags & TB_WAKE_ON_CONNECT) + val |= PORT_CS_19_WOC; + if (flags & TB_WAKE_ON_DISCONNECT) + val |= PORT_CS_19_WOD; + if (flags & TB_WAKE_ON_USB4) + val |= PORT_CS_19_WOU4; - up = tb_upstream_port(sw); - usb4_set_port_configured(up, false); -} + ret = tb_port_write(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1); + if (ret) + return ret; + } -/** - * usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding - * @sw: USB4 router - * - * Checks whether conditions are met so that lane bonding can be - * established with the upstream router. Call only for device routers. - */ -bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) -{ - struct tb_port *up; - int ret; - u32 val; + /* + * Enable wakes from PCIe and USB 3.x on this router. Only + * needed for device routers. + */ + if (route) { + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); + if (ret) + return ret; - up = tb_upstream_port(sw); - ret = tb_port_read(up, &val, TB_CFG_PORT, up->cap_usb4 + PORT_CS_18, 1); - if (ret) - return false; + val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU); + if (flags & TB_WAKE_ON_USB3) + val |= ROUTER_CS_5_WOU; + if (flags & TB_WAKE_ON_PCIE) + val |= ROUTER_CS_5_WOP; - return !!(val & PORT_CS_18_BE); + ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); + if (ret) + return ret; + } + + return 0; } /** * usb4_switch_set_sleep() - Prepare the router to enter sleep * @sw: USB4 router * - * Enables wakes and sets sleep bit for the router. Returns when the - * router sleep ready bit has been asserted. + * Sets sleep bit for the router. Returns when the router sleep ready + * bit has been asserted. */ int usb4_switch_set_sleep(struct tb_switch *sw) { @@ -795,6 +849,95 @@ int usb4_port_unlock(struct tb_port *port) return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1); } +static int usb4_port_set_configured(struct tb_port *port, bool configured) +{ + int ret; + u32 val; + + if (!port->cap_usb4) + return -EINVAL; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1); + if (ret) + return ret; + + if (configured) + val |= PORT_CS_19_PC; + else + val &= ~PORT_CS_19_PC; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1); +} + +/** + * usb4_port_configure() - Set USB4 port configured + * @port: USB4 router + * + * Sets the USB4 link to be configured for power management purposes. + */ +int usb4_port_configure(struct tb_port *port) +{ + return usb4_port_set_configured(port, true); +} + +/** + * usb4_port_unconfigure() - Set USB4 port unconfigured + * @port: USB4 router + * + * Sets the USB4 link to be unconfigured for power management purposes. + */ +void usb4_port_unconfigure(struct tb_port *port) +{ + usb4_port_set_configured(port, false); +} + +static int usb4_set_xdomain_configured(struct tb_port *port, bool configured) +{ + int ret; + u32 val; + + if (!port->cap_usb4) + return -EINVAL; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1); + if (ret) + return ret; + + if (configured) + val |= PORT_CS_19_PID; + else + val &= ~PORT_CS_19_PID; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_19, 1); +} + +/** + * usb4_port_configure_xdomain() - Configure port for XDomain + * @port: USB4 port connected to another host + * + * Marks the USB4 port as being connected to another host. Returns %0 in + * success and negative errno in failure. + */ +int usb4_port_configure_xdomain(struct tb_port *port) +{ + return usb4_set_xdomain_configured(port, true); +} + +/** + * usb4_port_unconfigure_xdomain() - Unconfigure port for XDomain + * @port: USB4 port that was connected to another host + * + * Clears USB4 port from being marked as XDomain. + */ +void usb4_port_unconfigure_xdomain(struct tb_port *port) +{ + usb4_set_xdomain_configured(port, false); +} + static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit, u32 value, int timeout_msec) { |