diff options
author | Mika Westerberg <mika.westerberg@linux.intel.com> | 2019-12-17 15:33:40 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-12-18 15:38:55 +0100 |
commit | b04079837b2094f09e145676eec4b9a56ae8a6aa (patch) | |
tree | 53c6fc96150e61db8225c2210eb2e28dd8a2672f /drivers | |
parent | 210e9f56e9e12472741b949950f9efcebf350750 (diff) |
thunderbolt: Add initial support for USB4
USB4 is the public specification based on Thunderbolt 3 protocol. There
are some differences in register layouts and flows. In addition to PCIe
and DP tunneling, USB4 supports tunneling of USB 3.x. USB4 is also
backward compatible with Thunderbolt 3 (and older generations but the
spec only talks about 3rd generation). USB4 compliant devices can be
identified by checking USB4 version field in router configuration space.
This patch adds initial support for USB4 compliant hosts and devices
which enables following features provided by the existing functionality
in the driver:
- PCIe tunneling
- Display Port tunneling
- Host and device NVM firmware upgrade
- P2P networking
This brings the USB4 support to the same level that we already have for
Thunderbolt 1, 2 and 3 devices.
Note the spec talks about host and device "routers" but in the driver we
still use term "switch" in most places. Both can be used interchangeably.
Co-developed-by: Rajmohan Mani <rajmohan.mani@intel.com>
Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Link: https://lore.kernel.org/r/20191217123345.31850-5-mika.westerberg@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/thunderbolt/Makefile | 2 | ||||
-rw-r--r-- | drivers/thunderbolt/eeprom.c | 53 | ||||
-rw-r--r-- | drivers/thunderbolt/nhi.c | 3 | ||||
-rw-r--r-- | drivers/thunderbolt/nhi.h | 2 | ||||
-rw-r--r-- | drivers/thunderbolt/switch.c | 382 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.c | 20 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.h | 36 | ||||
-rw-r--r-- | drivers/thunderbolt/tb_regs.h | 36 | ||||
-rw-r--r-- | drivers/thunderbolt/tunnel.c | 11 | ||||
-rw-r--r-- | drivers/thunderbolt/usb4.c | 724 | ||||
-rw-r--r-- | drivers/thunderbolt/xdomain.c | 6 |
11 files changed, 1158 insertions, 117 deletions
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 001187c577bf..c0b2fd73dfbd 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-${CONFIG_THUNDERBOLT} := 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 +thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o usb4.o diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 540e0105bcc0..921d164b3f35 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -487,6 +487,37 @@ err_free: return ret; } +static int usb4_copy_host_drom(struct tb_switch *sw, u16 *size) +{ + int ret; + + ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size)); + if (ret) + return ret; + + /* Size includes CRC8 + UID + CRC32 */ + *size += 1 + 8 + 4; + sw->drom = kzalloc(*size, GFP_KERNEL); + if (!sw->drom) + return -ENOMEM; + + ret = usb4_switch_drom_read(sw, 0, sw->drom, *size); + if (ret) { + kfree(sw->drom); + sw->drom = NULL; + } + + return ret; +} + +static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val, + size_t count) +{ + if (tb_switch_is_usb4(sw)) + return usb4_switch_drom_read(sw, offset, val, count); + return tb_eeprom_read_n(sw, offset, val, count); +} + /** * tb_drom_read - copy drom to sw->drom and parse it */ @@ -512,14 +543,26 @@ int tb_drom_read(struct tb_switch *sw) goto parse; /* - * The root switch contains only a dummy drom (header only, - * no entries). Hardcode the configuration here. + * USB4 hosts may support reading DROM through router + * operations. */ - tb_drom_read_uid_only(sw, &sw->uid); + if (tb_switch_is_usb4(sw)) { + usb4_switch_read_uid(sw, &sw->uid); + if (!usb4_copy_host_drom(sw, &size)) + goto parse; + } else { + /* + * The root switch contains only a dummy drom + * (header only, no entries). Hardcode the + * configuration here. + */ + tb_drom_read_uid_only(sw, &sw->uid); + } + return 0; } - res = tb_eeprom_read_n(sw, 14, (u8 *) &size, 2); + res = tb_drom_read_n(sw, 14, (u8 *) &size, 2); if (res) return res; size &= 0x3ff; @@ -533,7 +576,7 @@ int tb_drom_read(struct tb_switch *sw) sw->drom = kzalloc(size, GFP_KERNEL); if (!sw->drom) return -ENOMEM; - res = tb_eeprom_read_n(sw, 0, sw->drom, size); + res = tb_drom_read_n(sw, 0, sw->drom, size); if (res) goto err; diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 641b21b54460..1be491ecbb45 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1271,6 +1271,9 @@ static struct pci_device_id nhi_ids[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + /* Any USB4 compliant host */ + { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, + { 0,} }; diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index b7b973949f8e..5d276ee9b38e 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -74,4 +74,6 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_DEVICE_ID_INTEL_ICL_NHI1 0x8a0d #define PCI_DEVICE_ID_INTEL_ICL_NHI0 0x8a17 +#define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + #endif diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 9c72521cb298..c1d5cd7e0631 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -163,10 +163,12 @@ static int nvm_validate_and_write(struct tb_switch *sw) image_size -= hdr_size; } + if (tb_switch_is_usb4(sw)) + return usb4_switch_nvm_write(sw, 0, buf, image_size); return dma_port_flash_write(sw->dma_port, 0, buf, image_size); } -static int nvm_authenticate_host(struct tb_switch *sw) +static int nvm_authenticate_host_dma_port(struct tb_switch *sw) { int ret = 0; @@ -206,7 +208,7 @@ static int nvm_authenticate_host(struct tb_switch *sw) return ret; } -static int nvm_authenticate_device(struct tb_switch *sw) +static int nvm_authenticate_device_dma_port(struct tb_switch *sw) { int ret, retries = 10; @@ -251,6 +253,78 @@ static int nvm_authenticate_device(struct tb_switch *sw) return -ETIMEDOUT; } +static void nvm_authenticate_start_dma_port(struct tb_switch *sw) +{ + struct pci_dev *root_port; + + /* + * During host router NVM upgrade we should not allow root port to + * go into D3cold because some root ports cannot trigger PME + * itself. To be on the safe side keep the root port in D0 during + * the whole upgrade process. + */ + root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev); + if (root_port) + pm_runtime_get_noresume(&root_port->dev); +} + +static void nvm_authenticate_complete_dma_port(struct tb_switch *sw) +{ + struct pci_dev *root_port; + + root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev); + if (root_port) + pm_runtime_put(&root_port->dev); +} + +static inline bool nvm_readable(struct tb_switch *sw) +{ + if (tb_switch_is_usb4(sw)) { + /* + * USB4 devices must support NVM operations but it is + * optional for hosts. Therefore we query the NVM sector + * size here and if it is supported assume NVM + * operations are implemented. + */ + return usb4_switch_nvm_sector_size(sw) > 0; + } + + /* Thunderbolt 2 and 3 devices support NVM through DMA port */ + return !!sw->dma_port; +} + +static inline bool nvm_upgradeable(struct tb_switch *sw) +{ + if (sw->no_nvm_upgrade) + return false; + return nvm_readable(sw); +} + +static inline int nvm_read(struct tb_switch *sw, unsigned int address, + void *buf, size_t size) +{ + if (tb_switch_is_usb4(sw)) + return usb4_switch_nvm_read(sw, address, buf, size); + return dma_port_flash_read(sw->dma_port, address, buf, size); +} + +static int nvm_authenticate(struct tb_switch *sw) +{ + int ret; + + if (tb_switch_is_usb4(sw)) + return usb4_switch_nvm_authenticate(sw); + + if (!tb_route(sw)) { + nvm_authenticate_start_dma_port(sw); + ret = nvm_authenticate_host_dma_port(sw); + } else { + ret = nvm_authenticate_device_dma_port(sw); + } + + return ret; +} + static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val, size_t bytes) { @@ -264,7 +338,7 @@ static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val, goto out; } - ret = dma_port_flash_read(sw->dma_port, offset, val, bytes); + ret = nvm_read(sw, offset, val, bytes); mutex_unlock(&sw->tb->lock); out: @@ -341,9 +415,21 @@ static int tb_switch_nvm_add(struct tb_switch *sw) u32 val; int ret; - if (!sw->dma_port) + if (!nvm_readable(sw)) return 0; + /* + * The NVM format of non-Intel hardware is not known so + * currently restrict NVM upgrade for Intel hardware. We may + * relax this in the future when we learn other NVM formats. + */ + if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) { + dev_info(&sw->dev, + "NVM format of vendor %#x is not known, disabling NVM upgrade\n", + sw->config.vendor_id); + return 0; + } + nvm = kzalloc(sizeof(*nvm), GFP_KERNEL); if (!nvm) return -ENOMEM; @@ -358,8 +444,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw) if (!sw->safe_mode) { u32 nvm_size, hdr_size; - ret = dma_port_flash_read(sw->dma_port, NVM_FLASH_SIZE, &val, - sizeof(val)); + ret = nvm_read(sw, NVM_FLASH_SIZE, &val, sizeof(val)); if (ret) goto err_ida; @@ -367,8 +452,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw) nvm_size = (SZ_1M << (val & 7)) / 8; nvm_size = (nvm_size - hdr_size) / 2; - ret = dma_port_flash_read(sw->dma_port, NVM_VERSION, &val, - sizeof(val)); + ret = nvm_read(sw, NVM_VERSION, &val, sizeof(val)); if (ret) goto err_ida; @@ -620,6 +704,24 @@ int tb_port_clear_counter(struct tb_port *port, int counter) } /** + * tb_port_unlock() - Unlock downstream port + * @port: Port to unlock + * + * Needed for USB4 but can be called for any CIO/USB4 ports. Makes the + * downstream router accessible for CM. + */ +int tb_port_unlock(struct tb_port *port) +{ + if (tb_switch_is_icm(port->sw)) + return 0; + if (!tb_port_is_null(port)) + return -EINVAL; + if (tb_switch_is_usb4(port->sw)) + return usb4_port_unlock(port); + return 0; +} + +/** * tb_init_port() - initialize a port * * This is a helper method for tb_switch_alloc. Does not check or initialize @@ -650,6 +752,10 @@ static int tb_init_port(struct tb_port *port) port->cap_phy = cap; else tb_port_WARN(port, "non switch port without a PHY\n"); + + cap = tb_port_find_cap(port, TB_PORT_CAP_USB4); + if (cap > 0) + port->cap_usb4 = cap; } else if (port->port != 0) { cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP); if (cap > 0) @@ -1088,20 +1194,38 @@ int tb_dp_port_enable(struct tb_port *port, bool enable) /* switch utility functions */ -static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) +static const char *tb_switch_generation_name(const struct tb_switch *sw) +{ + switch (sw->generation) { + case 1: + return "Thunderbolt 1"; + case 2: + return "Thunderbolt 2"; + case 3: + return "Thunderbolt 3"; + case 4: + return "USB4"; + default: + return "Unknown"; + } +} + +static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw) { - tb_dbg(tb, " Switch: %x:%x (Revision: %d, TB Version: %d)\n", - sw->vendor_id, sw->device_id, sw->revision, - sw->thunderbolt_version); - tb_dbg(tb, " Max Port Number: %d\n", sw->max_port_number); + const struct tb_regs_switch_header *regs = &sw->config; + + tb_dbg(tb, " %s Switch: %x:%x (Revision: %d, TB Version: %d)\n", + tb_switch_generation_name(sw), regs->vendor_id, regs->device_id, + regs->revision, regs->thunderbolt_version); + tb_dbg(tb, " Max Port Number: %d\n", regs->max_port_number); tb_dbg(tb, " Config:\n"); tb_dbg(tb, " Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n", - sw->upstream_port_number, sw->depth, - (((u64) sw->route_hi) << 32) | sw->route_lo, - sw->enabled, sw->plug_events_delay); + regs->upstream_port_number, regs->depth, + (((u64) regs->route_hi) << 32) | regs->route_lo, + regs->enabled, regs->plug_events_delay); tb_dbg(tb, " unknown1: %#x unknown4: %#x\n", - sw->__unknown1, sw->__unknown4); + regs->__unknown1, regs->__unknown4); } /** @@ -1148,6 +1272,10 @@ 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; @@ -1359,30 +1487,6 @@ static ssize_t lanes_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL); static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL); -static void nvm_authenticate_start(struct tb_switch *sw) -{ - struct pci_dev *root_port; - - /* - * During host router NVM upgrade we should not allow root port to - * go into D3cold because some root ports cannot trigger PME - * itself. To be on the safe side keep the root port in D0 during - * the whole upgrade process. - */ - root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev); - if (root_port) - pm_runtime_get_noresume(&root_port->dev); -} - -static void nvm_authenticate_complete(struct tb_switch *sw) -{ - struct pci_dev *root_port; - - root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev); - if (root_port) - pm_runtime_put(&root_port->dev); -} - static ssize_t nvm_authenticate_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1431,17 +1535,7 @@ static ssize_t nvm_authenticate_store(struct device *dev, goto exit_unlock; sw->nvm->authenticating = true; - - if (!tb_route(sw)) { - /* - * Keep root port from suspending as long as the - * NVM upgrade process is running. - */ - nvm_authenticate_start(sw); - ret = nvm_authenticate_host(sw); - } else { - ret = nvm_authenticate_device(sw); - } + ret = nvm_authenticate(sw); } exit_unlock: @@ -1556,11 +1650,11 @@ static umode_t switch_attr_is_visible(struct kobject *kobj, return attr->mode; return 0; } else if (attr == &dev_attr_nvm_authenticate.attr) { - if (sw->dma_port && !sw->no_nvm_upgrade) + if (nvm_upgradeable(sw)) return attr->mode; return 0; } else if (attr == &dev_attr_nvm_version.attr) { - if (sw->dma_port) + if (nvm_readable(sw)) return attr->mode; return 0; } else if (attr == &dev_attr_boot.attr) { @@ -1672,6 +1766,9 @@ static int tb_switch_get_generation(struct tb_switch *sw) return 3; default: + if (tb_switch_is_usb4(sw)) + return 4; + /* * For unknown switches assume generation to be 1 to be * on the safe side. @@ -1682,6 +1779,19 @@ static int tb_switch_get_generation(struct tb_switch *sw) } } +static bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth) +{ + int max_depth; + + if (tb_switch_is_usb4(sw) || + (sw->tb->root_switch && tb_switch_is_usb4(sw->tb->root_switch))) + max_depth = USB4_SWITCH_MAX_DEPTH; + else + max_depth = TB_SWITCH_MAX_DEPTH; + + return depth > max_depth; +} + /** * tb_switch_alloc() - allocate a switch * @tb: Pointer to the owning domain @@ -1703,10 +1813,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, int upstream_port; int i, ret, depth; - /* Make sure we do not exceed maximum topology limit */ + /* Unlock the downstream port so we can access the switch below */ + if (route) { + struct tb_switch *parent_sw = tb_to_switch(parent); + struct tb_port *down; + + down = tb_port_at(route, parent_sw); + tb_port_unlock(down); + } + depth = tb_route_length(route); - if (depth > TB_SWITCH_MAX_DEPTH) - return ERR_PTR(-EADDRNOTAVAIL); upstream_port = tb_cfg_get_upstream_port(tb->ctl, route); if (upstream_port < 0) @@ -1721,8 +1837,10 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, if (ret) goto err_free_sw_ports; + sw->generation = tb_switch_get_generation(sw); + tb_dbg(tb, "current switch config:\n"); - tb_dump_switch(tb, &sw->config); + tb_dump_switch(tb, sw); /* configure switch */ sw->config.upstream_port_number = upstream_port; @@ -1731,6 +1849,10 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, sw->config.route_lo = lower_32_bits(route); sw->config.enabled = 0; + /* Make sure we do not exceed maximum topology limit */ + if (tb_switch_exceeds_max_depth(sw, depth)) + return ERR_PTR(-EADDRNOTAVAIL); + /* initialize ports */ sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), GFP_KERNEL); @@ -1745,14 +1867,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, sw->ports[i].port = i; } - sw->generation = tb_switch_get_generation(sw); - ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS); - if (ret < 0) { - tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n"); - goto err_free_sw_ports; - } - sw->cap_plug_events = ret; + if (ret > 0) + sw->cap_plug_events = ret; ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_LINK_CONTROLLER); if (ret > 0) @@ -1823,7 +1940,8 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route) * * Call this function before the switch is added to the system. It will * upload configuration to the switch and makes it available for the - * connection manager to use. + * connection manager to use. Can be called to the switch again after + * resume from low power states to re-initialize it. * * Return: %0 in case of success and negative errno in case of failure */ @@ -1834,21 +1952,50 @@ int tb_switch_configure(struct tb_switch *sw) int ret; route = tb_route(sw); - tb_dbg(tb, "initializing Switch at %#llx (depth: %d, up port: %d)\n", - route, tb_route_length(route), sw->config.upstream_port_number); - if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) - tb_sw_warn(sw, "unknown switch vendor id %#x\n", - sw->config.vendor_id); + tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n", + sw->config.enabled ? "restoring " : "initializing", route, + tb_route_length(route), sw->config.upstream_port_number); sw->config.enabled = 1; - /* upload configuration */ - ret = tb_sw_write(sw, 1 + (u32 *)&sw->config, TB_CFG_SWITCH, 1, 3); - if (ret) - return ret; + if (tb_switch_is_usb4(sw)) { + /* + * For USB4 devices, we need to program the CM version + * accordingly so that it knows to expose all the + * additional capabilities. + */ + sw->config.cmuv = USB4_VERSION_1_0; + + /* Enumerate the switch */ + ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH, + ROUTER_CS_1, 4); + if (ret) + return ret; - ret = tb_lc_configure_link(sw); + 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", + sw->config.vendor_id); + + if (!sw->cap_plug_events) { + tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n"); + return -ENODEV; + } + + /* 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; @@ -1857,18 +2004,32 @@ int tb_switch_configure(struct tb_switch *sw) static int tb_switch_set_uuid(struct tb_switch *sw) { + bool uid = false; u32 uuid[4]; int ret; if (sw->uuid) return 0; - /* - * The newer controllers include fused UUID as part of link - * controller specific registers - */ - ret = tb_lc_read_uuid(sw, uuid); - if (ret) { + if (tb_switch_is_usb4(sw)) { + ret = usb4_switch_read_uid(sw, &sw->uid); + if (ret) + return ret; + uid = true; + } else { + /* + * The newer controllers include fused UUID as part of + * link controller specific registers + */ + ret = tb_lc_read_uuid(sw, uuid); + if (ret) { + if (ret != -EINVAL) + return ret; + uid = true; + } + } + + if (uid) { /* * ICM generates UUID based on UID and fills the upper * two words with ones. This is not strictly following @@ -1935,7 +2096,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) nvm_get_auth_status(sw, &status); if (status) { if (!tb_route(sw)) - nvm_authenticate_complete(sw); + nvm_authenticate_complete_dma_port(sw); return 0; } @@ -1950,7 +2111,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) /* Now we can allow root port to suspend again */ if (!tb_route(sw)) - nvm_authenticate_complete(sw); + nvm_authenticate_complete_dma_port(sw); if (status) { tb_sw_info(sw, "switch flash authentication failed\n"); @@ -2004,6 +2165,8 @@ static bool tb_switch_lane_bonding_possible(struct tb_switch *sw) if (!up->dual_link_port || !up->dual_link_port->remote) return false; + if (tb_switch_is_usb4(sw)) + return usb4_switch_lane_bonding_possible(sw); return tb_lc_lane_bonding_possible(sw); } @@ -2240,7 +2403,11 @@ void tb_switch_remove(struct tb_switch *sw) if (!sw->is_unplugged) tb_plug_events_active(sw, false); - tb_lc_unconfigure_link(sw); + + if (tb_switch_is_usb4(sw)) + usb4_switch_unconfigure_link(sw); + else + tb_lc_unconfigure_link(sw); tb_switch_nvm_remove(sw); @@ -2298,7 +2465,10 @@ int tb_switch_resume(struct tb_switch *sw) return err; } - err = tb_drom_read_uid_only(sw, &uid); + if (tb_switch_is_usb4(sw)) + err = usb4_switch_read_uid(sw, &uid); + else + err = tb_drom_read_uid_only(sw, &uid); if (err) { tb_sw_warn(sw, "uid read failed\n"); return err; @@ -2311,16 +2481,7 @@ int tb_switch_resume(struct tb_switch *sw) } } - /* upload configuration */ - err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3); - if (err) - return err; - - err = tb_lc_configure_link(sw); - if (err) - return err; - - err = tb_plug_events_active(sw, true); + err = tb_switch_configure(sw); if (err) return err; @@ -2336,8 +2497,14 @@ 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)) { - if (tb_switch_resume(port->remote->sw)) { + } else if (tb_port_has_remote(port) || port->xdomain) { + /* + * Always unlock the port so the downstream + * switch/domain is accessible. + */ + if (tb_port_unlock(port)) + tb_port_warn(port, "failed to unlock port\n"); + if (port->remote && tb_switch_resume(port->remote->sw)) { tb_port_warn(port, "lost during suspend, disconnecting\n"); tb_sw_set_unplugged(port->remote->sw); @@ -2361,7 +2528,10 @@ void tb_switch_suspend(struct tb_switch *sw) tb_switch_suspend(port->remote->sw); } - tb_lc_set_sleep(sw); + if (tb_switch_is_usb4(sw)) + usb4_switch_set_sleep(sw); + else + tb_lc_set_sleep(sw); } /** @@ -2374,6 +2544,8 @@ void tb_switch_suspend(struct tb_switch *sw) */ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) { + if (tb_switch_is_usb4(sw)) + return usb4_switch_query_dp_resource(sw, in); return tb_lc_dp_sink_query(sw, in); } @@ -2388,6 +2560,8 @@ 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) { + if (tb_switch_is_usb4(sw)) + return usb4_switch_alloc_dp_resource(sw, in); return tb_lc_dp_sink_alloc(sw, in); } @@ -2401,10 +2575,16 @@ int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) */ void tb_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { - if (tb_lc_dp_sink_dealloc(sw, in)) { + int ret; + + if (tb_switch_is_usb4(sw)) + ret = usb4_switch_dealloc_dp_resource(sw, in); + else + ret = tb_lc_dp_sink_dealloc(sw, in); + + if (ret) tb_sw_warn(sw, "failed to de-allocate DP resource for port %d\n", in->port); - } } struct tb_sw_lookup { diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index e54d0d89a32d..6b99dcd1790c 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -365,12 +365,15 @@ static struct tb_port *tb_find_unused_port(struct tb_switch *sw, static struct tb_port *tb_find_pcie_down(struct tb_switch *sw, const struct tb_port *port) { + struct tb_port *down = NULL; + /* * To keep plugging devices consistently in the same PCIe - * hierarchy, do mapping here for root switch downstream PCIe - * ports. + * hierarchy, do mapping here for switch downstream PCIe ports. */ - if (!tb_route(sw)) { + if (tb_switch_is_usb4(sw)) { + down = usb4_switch_map_pcie_down(sw, port); + } else if (!tb_route(sw)) { int phy_port = tb_phy_port_from_link(port->port); int index; @@ -391,12 +394,17 @@ static struct tb_port *tb_find_pcie_down(struct tb_switch *sw, /* Validate the hard-coding */ if (WARN_ON(index > sw->config.max_port_number)) goto out; - if (WARN_ON(!tb_port_is_pcie_down(&sw->ports[index]))) + + down = &sw->ports[index]; + } + + if (down) { + if (WARN_ON(!tb_port_is_pcie_down(down))) goto out; - if (WARN_ON(tb_pci_port_is_enabled(&sw->ports[index]))) + if (WARN_ON(tb_pci_port_is_enabled(down))) goto out; - return &sw->ports[index]; + return down; } out: diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index ade1c7c77db1..0158f0e9858c 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -44,6 +44,7 @@ struct tb_switch_nvm { #define TB_SWITCH_KEY_SIZE 32 #define TB_SWITCH_MAX_DEPTH 6 +#define USB4_SWITCH_MAX_DEPTH 5 /** * struct tb_switch - a thunderbolt switch @@ -129,6 +130,7 @@ struct tb_switch { * @xdomain: Remote host (%NULL if not connected) * @cap_phy: Offset, zero if not found * @cap_adap: Offset of the adapter specific capability (%0 if not present) + * @cap_usb4: Offset to the USB4 port capability (%0 if not present) * @port: Port number on switch * @disabled: Disabled by eeprom * @bonded: true if the port is bonded (two lanes combined as one) @@ -146,6 +148,7 @@ struct tb_port { struct tb_xdomain *xdomain; int cap_phy; int cap_adap; + int cap_usb4; u8 port; bool disabled; bool bonded; @@ -638,6 +641,17 @@ static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw) } /** + * tb_switch_is_usb4() - Is the switch USB4 compliant + * @sw: Switch to check + * + * Returns true if the @sw is USB4 compliant router, false otherwise. + */ +static inline bool tb_switch_is_usb4(const struct tb_switch *sw) +{ + return sw->config.thunderbolt_version == USB4_VERSION_1_0; +} + +/** * tb_switch_is_icm() - Is the switch handled by ICM firmware * @sw: Switch to check * @@ -662,6 +676,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); 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_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); @@ -736,4 +751,25 @@ 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 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_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_write(struct tb_switch *sw, unsigned int address, + const void *buf, size_t size); +int usb4_switch_nvm_authenticate(struct tb_switch *sw); +bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in); +int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in); +int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in); +struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, + const struct tb_port *port); + +int usb4_port_unlock(struct tb_port *port); #endif diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 7ee45b73c7f7..47f73f992412 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -41,6 +41,7 @@ enum tb_port_cap { TB_PORT_CAP_TIME1 = 0x03, TB_PORT_CAP_ADAP = 0x04, TB_PORT_CAP_VSE = 0x05, + TB_PORT_CAP_USB4 = 0x06, }; enum tb_port_state { @@ -164,10 +165,36 @@ struct tb_regs_switch_header { * milliseconds. Writing 0x00 is interpreted * as 255ms. */ - u32 __unknown4:16; + u32 cmuv:8; + u32 __unknown4:8; u32 thunderbolt_version:8; } __packed; +/* USB4 version 1.0 */ +#define USB4_VERSION_1_0 0x20 + +#define ROUTER_CS_1 0x01 +#define ROUTER_CS_4 0x04 +#define ROUTER_CS_5 0x05 +#define ROUTER_CS_5_SLP BIT(0) +#define ROUTER_CS_5_C3S BIT(23) +#define ROUTER_CS_5_PTO BIT(24) +#define ROUTER_CS_5_HCO BIT(26) +#define ROUTER_CS_5_CV BIT(31) +#define ROUTER_CS_6 0x06 +#define ROUTER_CS_6_SLPR BIT(0) +#define ROUTER_CS_6_TNS BIT(1) +#define ROUTER_CS_6_HCI BIT(18) +#define ROUTER_CS_6_CR BIT(25) +#define ROUTER_CS_7 0x07 +#define ROUTER_CS_9 0x09 +#define ROUTER_CS_25 0x19 +#define ROUTER_CS_26 0x1a +#define ROUTER_CS_26_STATUS_MASK GENMASK(29, 24) +#define ROUTER_CS_26_STATUS_SHIFT 24 +#define ROUTER_CS_26_ONS BIT(30) +#define ROUTER_CS_26_OV BIT(31) + enum tb_port_type { TB_TYPE_INACTIVE = 0x000000, TB_TYPE_PORT = 0x000001, @@ -216,6 +243,7 @@ struct tb_regs_port_header { #define ADP_CS_4_NFC_BUFFERS_MASK GENMASK(9, 0) #define ADP_CS_4_TOTAL_BUFFERS_MASK GENMASK(29, 20) #define ADP_CS_4_TOTAL_BUFFERS_SHIFT 20 +#define ADP_CS_4_LCK BIT(31) #define ADP_CS_5 0x05 #define ADP_CS_5_LCA_MASK GENMASK(28, 22) #define ADP_CS_5_LCA_SHIFT 22 @@ -237,6 +265,12 @@ struct tb_regs_port_header { #define LANE_ADP_CS_1_CURRENT_WIDTH_MASK GENMASK(25, 20) #define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT 20 +/* USB4 port registers */ +#define PORT_CS_18 0x12 +#define PORT_CS_18_BE BIT(8) +#define PORT_CS_19 0x13 +#define PORT_CS_19_PC BIT(3) + /* Display Port adapter registers */ #define ADP_DP_CS_0 0x00 #define ADP_DP_CS_0_VIDEO_HOPID_MASK GENMASK(26, 16) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 0d3463c4e24a..21d266a76b7d 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -243,6 +243,12 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, return tunnel; } +static bool tb_dp_is_usb4(const struct tb_switch *sw) +{ + /* Titan Ridge DP adapters need the same treatment as USB4 */ + return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw); +} + static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out) { int timeout = 10; @@ -250,8 +256,7 @@ static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out) int ret; /* Both ends need to support this */ - if (!tb_switch_is_titan_ridge(in->sw) || - !tb_switch_is_titan_ridge(out->sw)) + if (!tb_dp_is_usb4(in->sw) || !tb_dp_is_usb4(out->sw)) return 0; ret = tb_port_read(out, &val, TB_CFG_PORT, @@ -531,7 +536,7 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel) u32 val, rate = 0, lanes = 0; int ret; - if (tb_switch_is_titan_ridge(sw)) { + if (tb_dp_is_usb4(sw)) { int timeout = 10; /* diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c new file mode 100644 index 000000000000..b84c74346d2b --- /dev/null +++ b/drivers/thunderbolt/usb4.c @@ -0,0 |