From fe6f6f95919ccdc8a3e2c5dae9b1740583e16523 Mon Sep 17 00:00:00 2001 From: Crag Wang Date: Thu, 27 May 2021 16:45:03 +0800 Subject: thunderbolt: Add self-authenticate support for new dock Add new device known to support self-authenticate on disconnect. Signed-off-by: Crag Wang Reviewed-by: Mario Limonciello Signed-off-by: Mika Westerberg --- drivers/thunderbolt/quirks.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 57e2978a3c21..892cf0e8ada5 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -21,6 +21,7 @@ struct tb_quirk { static const struct tb_quirk tb_quirks[] = { /* Dell WD19TB supports self-authentication on unplug */ { 0x00d4, 0xb070, quirk_force_power_link }, + { 0x00d4, 0xb071, quirk_force_power_link }, }; /** -- cgit v1.2.3 From 9b383037770fcd6b03dd3793c89055b8490957e0 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 1 Apr 2021 16:54:15 +0300 Subject: thunderbolt: Split NVM read/write generic functions out from usb4.c We do this for Thunderbolt 2/3 devices through DMA port, USB4 devices and retimers pretty much the same way. Only the actual block read/write is different. For this reason split out the NVM read/write functions from usb4.c to nvm.c and make USB4 device code call these when needed. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nvm.c | 95 +++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 11 +++++ drivers/thunderbolt/usb4.c | 110 ++++++++------------------------------------- 3 files changed, 124 insertions(+), 92 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c index 29de6d95c6e7..3a5336913cca 100644 --- a/drivers/thunderbolt/nvm.c +++ b/drivers/thunderbolt/nvm.c @@ -164,6 +164,101 @@ void tb_nvm_free(struct tb_nvm *nvm) kfree(nvm); } +/** + * tb_nvm_read_data() - Read data from NVM + * @address: Start address on the flash + * @buf: Buffer where the read data is copied + * @size: Size of the buffer in bytes + * @retries: Number of retries if block read fails + * @read_block: Function that reads block from the flash + * @read_block_data: Data passsed to @read_block + * + * This is a generic function that reads data from NVM or NVM like + * device. + * + * Returns %0 on success and negative errno otherwise. + */ +int tb_nvm_read_data(unsigned int address, void *buf, size_t size, + unsigned int retries, read_block_fn read_block, + void *read_block_data) +{ + do { + unsigned int dwaddress, dwords, offset; + u8 data[NVM_DATA_DWORDS * 4]; + size_t nbytes; + int ret; + + offset = address & 3; + nbytes = min_t(size_t, size + offset, NVM_DATA_DWORDS * 4); + + dwaddress = address / 4; + dwords = ALIGN(nbytes, 4) / 4; + + ret = read_block(read_block_data, dwaddress, data, dwords); + if (ret) { + if (ret != -ENODEV && retries--) + continue; + return ret; + } + + nbytes -= offset; + memcpy(buf, data + offset, nbytes); + + size -= nbytes; + address += nbytes; + buf += nbytes; + } while (size > 0); + + return 0; +} + +/** + * tb_nvm_write_data() - Write data to NVM + * @address: Start address on the flash + * @buf: Buffer where the data is copied from + * @size: Size of the buffer in bytes + * @retries: Number of retries if the block write fails + * @write_block: Function that writes block to the flash + * @write_block_data: Data passwd to @write_block + * + * This is generic function that writes data to NVM or NVM like device. + * + * Returns %0 on success and negative errno otherwise. + */ +int tb_nvm_write_data(unsigned int address, const void *buf, size_t size, + unsigned int retries, write_block_fn write_block, + void *write_block_data) +{ + do { + unsigned int offset, dwaddress; + u8 data[NVM_DATA_DWORDS * 4]; + size_t nbytes; + int ret; + + offset = address & 3; + nbytes = min_t(u32, size + offset, NVM_DATA_DWORDS * 4); + + memcpy(data + offset, buf, nbytes); + + dwaddress = address / 4; + ret = write_block(write_block_data, dwaddress, data, nbytes / 4); + if (ret) { + if (ret == -ETIMEDOUT) { + if (retries--) + continue; + ret = -EIO; + } + return ret; + } + + size -= nbytes; + address += nbytes; + buf += nbytes; + } while (size > 0); + + return 0; +} + void tb_nvm_exit(void) { ida_destroy(&nvm_ida); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 9790e9f13d2b..d9d1adc4cfd3 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -20,6 +20,7 @@ #define NVM_MIN_SIZE SZ_32K #define NVM_MAX_SIZE SZ_512K +#define NVM_DATA_DWORDS 16 /* Intel specific NVM offsets */ #define NVM_DEVID 0x05 @@ -674,6 +675,16 @@ int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size, void tb_nvm_free(struct tb_nvm *nvm); void tb_nvm_exit(void); +typedef int (*read_block_fn)(void *, unsigned int, void *, size_t); +typedef int (*write_block_fn)(void *, unsigned int, const void *, size_t); + +int tb_nvm_read_data(unsigned int address, void *buf, size_t size, + unsigned int retries, read_block_fn read_block, + void *read_block_data); +int tb_nvm_write_data(unsigned int address, const void *buf, size_t size, + unsigned int retries, write_block_fn write_next_block, + void *write_block_data); + struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, u64 route); struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb, diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 671d72af8ba1..c0a14bfa36d4 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -13,7 +13,6 @@ #include "sb_regs.h" #include "tb.h" -#define USB4_DATA_DWORDS 16 #define USB4_DATA_RETRIES 3 enum usb4_sb_target { @@ -37,9 +36,6 @@ enum usb4_sb_target { #define USB4_NVM_SECTOR_SIZE_MASK GENMASK(23, 0) -typedef int (*read_block_fn)(void *, unsigned int, void *, size_t); -typedef int (*write_block_fn)(void *, const void *, size_t); - static int usb4_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, u32 value, int timeout_msec) { @@ -62,76 +58,6 @@ static int usb4_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, return -ETIMEDOUT; } -static int usb4_do_read_data(u16 address, void *buf, size_t size, - read_block_fn read_block, void *read_block_data) -{ - unsigned int retries = USB4_DATA_RETRIES; - unsigned int offset; - - do { - unsigned int dwaddress, dwords; - u8 data[USB4_DATA_DWORDS * 4]; - size_t nbytes; - int ret; - - offset = address & 3; - nbytes = min_t(size_t, size + offset, USB4_DATA_DWORDS * 4); - - dwaddress = address / 4; - dwords = ALIGN(nbytes, 4) / 4; - - ret = read_block(read_block_data, dwaddress, data, dwords); - if (ret) { - if (ret != -ENODEV && retries--) - continue; - return ret; - } - - nbytes -= offset; - memcpy(buf, data + offset, nbytes); - - size -= nbytes; - address += nbytes; - buf += nbytes; - } while (size > 0); - - return 0; -} - -static int usb4_do_write_data(unsigned int address, const void *buf, size_t size, - write_block_fn write_next_block, void *write_block_data) -{ - unsigned int retries = USB4_DATA_RETRIES; - unsigned int offset; - - offset = address & 3; - address = address & ~3; - - do { - u32 nbytes = min_t(u32, size, USB4_DATA_DWORDS * 4); - u8 data[USB4_DATA_DWORDS * 4]; - int ret; - - memcpy(data + offset, buf, nbytes); - - ret = write_next_block(write_block_data, data, nbytes / 4); - if (ret) { - if (ret == -ETIMEDOUT) { - if (retries--) - continue; - ret = -EIO; - } - return ret; - } - - size -= nbytes; - address += nbytes; - buf += nbytes; - } while (size > 0); - - return 0; -} - static int usb4_native_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, u8 *status, const void *tx_data, size_t tx_dwords, @@ -193,7 +119,7 @@ static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, { const struct tb_cm_ops *cm_ops = sw->tb->cm_ops; - if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS) + if (tx_dwords > NVM_DATA_DWORDS || rx_dwords > NVM_DATA_DWORDS) return -EINVAL; /* @@ -414,8 +340,8 @@ static int usb4_switch_drom_read_block(void *data, int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) { - return usb4_do_read_data(address, buf, size, - usb4_switch_drom_read_block, sw); + return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES, + usb4_switch_drom_read_block, sw); } /** @@ -595,8 +521,8 @@ static int usb4_switch_nvm_read_block(void *data, int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) { - return usb4_do_read_data(address, buf, size, - usb4_switch_nvm_read_block, sw); + return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES, + usb4_switch_nvm_read_block, sw); } static int usb4_switch_nvm_set_offset(struct tb_switch *sw, @@ -618,8 +544,8 @@ static int usb4_switch_nvm_set_offset(struct tb_switch *sw, return status ? -EIO : 0; } -static int usb4_switch_nvm_write_next_block(void *data, const void *buf, - size_t dwords) +static int usb4_switch_nvm_write_next_block(void *data, unsigned int dwaddress, + const void *buf, size_t dwords) { struct tb_switch *sw = data; u8 status; @@ -652,8 +578,8 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, if (ret) return ret; - return usb4_do_write_data(address, buf, size, - usb4_switch_nvm_write_next_block, sw); + return tb_nvm_write_data(address, buf, size, USB4_DATA_RETRIES, + usb4_switch_nvm_write_next_block, sw); } /** @@ -1029,7 +955,7 @@ static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit, static int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords) { - if (dwords > USB4_DATA_DWORDS) + if (dwords > NVM_DATA_DWORDS) return -EINVAL; return tb_port_read(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, @@ -1039,7 +965,7 @@ static int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords) static int usb4_port_write_data(struct tb_port *port, const void *data, size_t dwords) { - if (dwords > USB4_DATA_DWORDS) + if (dwords > NVM_DATA_DWORDS) return -EINVAL; return tb_port_write(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, @@ -1316,8 +1242,8 @@ struct retimer_info { u8 index; }; -static int usb4_port_retimer_nvm_write_next_block(void *data, const void *buf, - size_t dwords) +static int usb4_port_retimer_nvm_write_next_block(void *data, + unsigned int dwaddress, const void *buf, size_t dwords) { const struct retimer_info *info = data; @@ -1357,8 +1283,8 @@ int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int add if (ret) return ret; - return usb4_do_write_data(address, buf, size, - usb4_port_retimer_nvm_write_next_block, &info); + return tb_nvm_write_data(address, buf, size, USB4_DATA_RETRIES, + usb4_port_retimer_nvm_write_next_block, &info); } /** @@ -1442,7 +1368,7 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress, int ret; metadata = dwaddress << USB4_NVM_READ_OFFSET_SHIFT; - if (dwords < USB4_DATA_DWORDS) + if (dwords < NVM_DATA_DWORDS) metadata |= dwords << USB4_NVM_READ_LENGTH_SHIFT; ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata, @@ -1475,8 +1401,8 @@ int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index, { struct retimer_info info = { .port = port, .index = index }; - return usb4_do_read_data(address, buf, size, - usb4_port_retimer_nvm_read_block, &info); + return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES, + usb4_port_retimer_nvm_read_block, &info); } /** -- cgit v1.2.3 From 34163dfad412947c1a413324d0293f1da219231f Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 1 Apr 2021 16:57:06 +0300 Subject: thunderbolt: Use generic tb_nvm_[read|write]_data() for Thunderbolt 2/3 devices Now that we have generic functionality available in nvm.c make the DMA port code call it instead of duplicating the functionality. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_port.c | 94 +++++++----------------------------------- 1 file changed, 15 insertions(+), 79 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c index 5631319f7b20..9f20c7bbf0ce 100644 --- a/drivers/thunderbolt/dma_port.c +++ b/drivers/thunderbolt/dma_port.c @@ -299,15 +299,13 @@ static int dma_port_request(struct tb_dma_port *dma, u32 in, return status_to_errno(out); } -static int dma_port_flash_read_block(struct tb_dma_port *dma, u32 address, - void *buf, u32 size) +static int dma_port_flash_read_block(void *data, unsigned int dwaddress, + void *buf, size_t dwords) { + struct tb_dma_port *dma = data; struct tb_switch *sw = dma->sw; - u32 in, dwaddress, dwords; int ret; - - dwaddress = address / 4; - dwords = size / 4; + u32 in; in = MAIL_IN_CMD_FLASH_READ << MAIL_IN_CMD_SHIFT; if (dwords < MAIL_DATA_DWORDS) @@ -323,14 +321,13 @@ static int dma_port_flash_read_block(struct tb_dma_port *dma, u32 address, dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT); } -static int dma_port_flash_write_block(struct tb_dma_port *dma, u32 address, - const void *buf, u32 size) +static int dma_port_flash_write_block(void *data, unsigned int dwaddress, + const void *buf, size_t dwords) { + struct tb_dma_port *dma = data; struct tb_switch *sw = dma->sw; - u32 in, dwaddress, dwords; int ret; - - dwords = size / 4; + u32 in; /* Write the block to MAIL_DATA registers */ ret = dma_port_write(sw->tb->ctl, buf, tb_route(sw), dma->port, @@ -341,12 +338,8 @@ static int dma_port_flash_write_block(struct tb_dma_port *dma, u32 address, in = MAIL_IN_CMD_FLASH_WRITE << MAIL_IN_CMD_SHIFT; /* CSS header write is always done to the same magic address */ - if (address >= DMA_PORT_CSS_ADDRESS) { - dwaddress = DMA_PORT_CSS_ADDRESS; + if (dwaddress >= DMA_PORT_CSS_ADDRESS) in |= MAIL_IN_CSS; - } else { - dwaddress = address / 4; - } in |= ((dwords - 1) << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK; in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK; @@ -365,36 +358,8 @@ static int dma_port_flash_write_block(struct tb_dma_port *dma, u32 address, int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address, void *buf, size_t size) { - unsigned int retries = DMA_PORT_RETRIES; - - do { - unsigned int offset; - size_t nbytes; - int ret; - - offset = address & 3; - nbytes = min_t(size_t, size + offset, MAIL_DATA_DWORDS * 4); - - ret = dma_port_flash_read_block(dma, address, dma->buf, - ALIGN(nbytes, 4)); - if (ret) { - if (ret == -ETIMEDOUT) { - if (retries--) - continue; - ret = -EIO; - } - return ret; - } - - nbytes -= offset; - memcpy(buf, dma->buf + offset, nbytes); - - size -= nbytes; - address += nbytes; - buf += nbytes; - } while (size > 0); - - return 0; + return tb_nvm_read_data(address, buf, size, DMA_PORT_RETRIES, + dma_port_flash_read_block, dma); } /** @@ -411,40 +376,11 @@ int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address, int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address, const void *buf, size_t size) { - unsigned int retries = DMA_PORT_RETRIES; - unsigned int offset; - - if (address >= DMA_PORT_CSS_ADDRESS) { - offset = 0; - if (size > DMA_PORT_CSS_MAX_SIZE) - return -E2BIG; - } else { - offset = address & 3; - address = address & ~3; - } - - do { - u32 nbytes = min_t(u32, size, MAIL_DATA_DWORDS * 4); - int ret; + if (address >= DMA_PORT_CSS_ADDRESS && size > DMA_PORT_CSS_MAX_SIZE) + return -E2BIG; - memcpy(dma->buf + offset, buf, nbytes); - - ret = dma_port_flash_write_block(dma, address, buf, nbytes); - if (ret) { - if (ret == -ETIMEDOUT) { - if (retries--) - continue; - ret = -EIO; - } - return ret; - } - - size -= nbytes; - address += nbytes; - buf += nbytes; - } while (size > 0); - - return 0; + return tb_nvm_write_data(address, buf, size, DMA_PORT_RETRIES, + dma_port_flash_write_block, dma); } /** -- cgit v1.2.3 From 6026b703e8f61bf9c395bb286fa3b46956ce0496 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 14 Jan 2021 16:44:17 +0200 Subject: thunderbolt: Add wake from DisplayPort Latest USB4 spec added a new wake bit for DisplayPort so add this to the driver when runtime suspending. This way wake up the domain when a new monitor is plugged in to any of the device routers. Also do the same for pre-USB4 devices through the link controller registers as documented in chapter 13 of the USB4 spec. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/lc.c | 6 ++++-- drivers/thunderbolt/switch.c | 3 ++- drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/tb_regs.h | 3 +++ drivers/thunderbolt/usb4.c | 6 ++++-- 5 files changed, 14 insertions(+), 5 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c index bc671730a11f..c178f0d7beab 100644 --- a/drivers/thunderbolt/lc.c +++ b/drivers/thunderbolt/lc.c @@ -208,8 +208,8 @@ static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, if (ret) return ret; - ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WOP | - TB_LC_SX_CTRL_WOU4); + ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WODPC | + TB_LC_SX_CTRL_WODPD | 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; @@ -217,6 +217,8 @@ static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, ctrl |= TB_LC_SX_CTRL_WOU4; if (flags & TB_WAKE_ON_PCIE) ctrl |= TB_LC_SX_CTRL_WOP; + if (flags & TB_WAKE_ON_DP) + ctrl |= TB_LC_SX_CTRL_WODPC | TB_LC_SX_CTRL_WODPD; return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, offset + TB_LC_SX_CTRL, 1); } diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e73cd296db7e..4d4bc50a3c44 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2844,7 +2844,8 @@ void tb_switch_suspend(struct tb_switch *sw, bool 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; + flags |= TB_WAKE_ON_USB4; + flags |= TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE | TB_WAKE_ON_DP; } else if (device_may_wakeup(&sw->dev)) { flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE; } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index d9d1adc4cfd3..60a987c748ca 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -347,6 +347,7 @@ struct tb_path { #define TB_WAKE_ON_USB4 BIT(2) #define TB_WAKE_ON_USB3 BIT(3) #define TB_WAKE_ON_PCIE BIT(4) +#define TB_WAKE_ON_DP BIT(5) /** * struct tb_cm_ops - Connection manager specific operations vector diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 626751e06292..113d7903b183 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -195,6 +195,7 @@ struct tb_regs_switch_header { #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_WOD BIT(3) #define ROUTER_CS_5_C3S BIT(23) #define ROUTER_CS_5_PTO BIT(24) #define ROUTER_CS_5_UTO BIT(25) @@ -458,6 +459,8 @@ struct tb_regs_hop { #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_WODPC BIT(3) +#define TB_LC_SX_CTRL_WODPD BIT(4) #define TB_LC_SX_CTRL_WOU4 BIT(5) #define TB_LC_SX_CTRL_WOP BIT(6) #define TB_LC_SX_CTRL_L1C BIT(16) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index c0a14bfa36d4..7e8b5ca3114c 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -413,7 +413,7 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags) } /* - * Enable wakes from PCIe and USB 3.x on this router. Only + * Enable wakes from PCIe, USB 3.x and DP on this router. Only * needed for device routers. */ if (route) { @@ -421,11 +421,13 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags) if (ret) return ret; - val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU); + val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU | ROUTER_CS_5_WOD); if (flags & TB_WAKE_ON_USB3) val |= ROUTER_CS_5_WOU; if (flags & TB_WAKE_ON_PCIE) val |= ROUTER_CS_5_WOP; + if (flags & TB_WAKE_ON_DP) + val |= ROUTER_CS_5_WOD; ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); if (ret) -- cgit v1.2.3 From 3caf88871c6ad0bf7c5fca8a6f7fcada1891a891 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 14 Jan 2021 16:41:31 +0200 Subject: thunderbolt: Align USB4 router wakes configuration with the CM guide The USB4 Configuration Manager guide suggests that the USB4 port wakes are configured in a certain way, like that when the port is configured the wake-on-connect should not be set and so forth, so align the driver with this. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 7e8b5ca3114c..b56af7b0a093 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -399,12 +399,18 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags) val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4); - 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) + if (tb_is_upstream_port(port)) { val |= PORT_CS_19_WOU4; + } else { + bool configured = val & PORT_CS_19_PC; + + if ((flags & TB_WAKE_ON_CONNECT) && !configured) + val |= PORT_CS_19_WOC; + if ((flags & TB_WAKE_ON_DISCONNECT) && configured) + val |= PORT_CS_19_WOD; + if ((flags & TB_WAKE_ON_USB4) && configured) + val |= PORT_CS_19_WOU4; + } ret = tb_port_write(port, &val, TB_CFG_PORT, port->cap_usb4 + PORT_CS_19, 1); -- cgit v1.2.3 From 1c561e4e659d59f1f2825dec42f09338eac1c774 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 10 Dec 2020 16:55:17 +0200 Subject: thunderbolt: Make tb_port_type() take const parameter The function does not modify the object in any way so make the parameter const to reflect this. No functional changes intended. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 4d4bc50a3c44..0edc452c2ac9 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -459,7 +459,7 @@ static void tb_switch_nvm_remove(struct tb_switch *sw) /* port utility functions */ -static const char *tb_port_type(struct tb_regs_port_header *port) +static const char *tb_port_type(const struct tb_regs_port_header *port) { switch (port->type >> 16) { case 0: -- cgit v1.2.3 From 02c5e7c2db2bdfe227dd3e7f6febd732ccec5440 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 10 Dec 2020 16:07:59 +0200 Subject: thunderbolt: Move nfc_credits field to struct tb_path_hop With the USB4 buffer allocation the number of credits (and non-flow credits) may be different depending on the router buffer allocation preferences. To allow this move the nfc_credits field to struct tb_path_hop. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/path.c | 4 ++-- drivers/thunderbolt/tb.h | 5 +++-- drivers/thunderbolt/tunnel.c | 25 ++++++++++++++----------- 3 files changed, 19 insertions(+), 15 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index f63e205a35d9..564e2f42cebd 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -367,7 +367,7 @@ static void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop) int i, res; for (i = first_hop; i < path->path_length; i++) { res = tb_port_add_nfc_credits(path->hops[i].in_port, - -path->nfc_credits); + -path->hops[i].nfc_credits); if (res) tb_port_warn(path->hops[i].in_port, "nfc credits deallocation failed for hop %d\n", @@ -502,7 +502,7 @@ int tb_path_activate(struct tb_path *path) /* Add non flow controlled credits. */ for (i = path->path_length - 1; i >= 0; i--) { res = tb_port_add_nfc_credits(path->hops[i].in_port, - path->nfc_credits); + path->hops[i].nfc_credits); if (res) { __tb_path_deallocate_nfc(path, i); goto err; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 60a987c748ca..b4bc25b82fdb 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -256,6 +256,8 @@ struct tb_retimer { * @next_hop_index: HopID of the packet when it is routed out from @out_port * @initial_credits: Number of initial flow control credits allocated for * the path + * @nfc_credits: Number of non-flow controlled buffers allocated for the + * @in_port. * * Hop configuration is always done on the IN port of a switch. * in_port and out_port have to be on the same switch. Packets arriving on @@ -275,6 +277,7 @@ struct tb_path_hop { int in_counter_index; int next_hop_index; unsigned int initial_credits; + unsigned int nfc_credits; }; /** @@ -297,7 +300,6 @@ enum tb_path_port { * struct tb_path - a unidirectional path between two ports * @tb: Pointer to the domain structure * @name: Name of the path (used for debugging) - * @nfc_credits: Number of non flow controlled credits allocated for the path * @ingress_shared_buffer: Shared buffering used for ingress ports on the path * @egress_shared_buffer: Shared buffering used for egress ports on the path * @ingress_fc_enable: Flow control for ingress ports on the path @@ -318,7 +320,6 @@ enum tb_path_port { struct tb_path { struct tb *tb; const char *name; - int nfc_credits; enum tb_path_port ingress_shared_buffer; enum tb_path_port egress_shared_buffer; enum tb_path_port ingress_fc_enable; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index e1979bed7146..5be0f31949f1 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -119,7 +119,6 @@ static void tb_pci_init_path(struct tb_path *path) path->priority = 3; path->weight = 1; path->drop_packages = 0; - path->nfc_credits = 0; path->hops[0].initial_credits = 7; if (path->path_length > 1) path->hops[1].initial_credits = @@ -616,7 +615,7 @@ static void tb_dp_init_aux_path(struct tb_path *path) static void tb_dp_init_video_path(struct tb_path *path, bool discover) { - u32 nfc_credits = path->hops[0].in_port->config.nfc_credits; + int i; path->egress_fc_enable = TB_PATH_NONE; path->egress_shared_buffer = TB_PATH_NONE; @@ -625,15 +624,20 @@ static void tb_dp_init_video_path(struct tb_path *path, bool discover) path->priority = 1; path->weight = 1; - if (discover) { - path->nfc_credits = nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK; - } else { - u32 max_credits; + for (i = 0; i < path->path_length; i++) { + u32 nfc_credits = path->hops[i].in_port->config.nfc_credits; - max_credits = (nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >> - ADP_CS_4_TOTAL_BUFFERS_SHIFT; - /* Leave some credits for AUX path */ - path->nfc_credits = min(max_credits - 2, 12U); + if (discover) { + path->hops[i].nfc_credits = + nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK; + } else { + u32 max_credits; + + max_credits = (nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >> + ADP_CS_4_TOTAL_BUFFERS_SHIFT; + /* Leave some credits for AUX path */ + path->hops[i].nfc_credits = min(max_credits - 2, 12U); + } } } @@ -1076,7 +1080,6 @@ static void tb_usb3_init_path(struct tb_path *path) path->priority = 3; path->weight = 3; path->drop_packages = 0; - path->nfc_credits = 0; path->hops[0].initial_credits = 7; if (path->path_length > 1) path->hops[1].initial_credits = -- cgit v1.2.3 From e7051beab8393dc614f7ea3969aa03bc490db1d6 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 22 Mar 2021 16:54:54 +0200 Subject: thunderbolt: Wait for the lanes to actually bond It may take some time until the two lanes enter bonded state so poll for the link width to match what is expected before going forward. This ensures the link is in expected state before we start establishing paths through it. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 50 +++++++++++++++++++++++++++++++++++++++++-- drivers/thunderbolt/tb.h | 2 ++ drivers/thunderbolt/xdomain.c | 8 +++++++ 3 files changed, 58 insertions(+), 2 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 0edc452c2ac9..d5420eefe25d 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -991,8 +991,11 @@ static int tb_port_set_link_width(struct tb_port *port, unsigned int width) * tb_port_lane_bonding_enable() - Enable bonding on port * @port: port to enable * - * Enable bonding by setting the link width of the port and the - * other port in case of dual link port. + * Enable bonding by setting the link width of the port and the other + * port in case of dual link port. Does not wait for the link to + * actually reach the bonded state so caller needs to call + * tb_port_wait_for_link_width() before enabling any paths through the + * link to make sure the link is in expected state. * * Return: %0 in case of success and negative errno in case of error */ @@ -1043,6 +1046,36 @@ void tb_port_lane_bonding_disable(struct tb_port *port) tb_port_set_link_width(port, 1); } +/** + * tb_port_wait_for_link_width() - Wait until link reaches specific width + * @port: Port to wait for + * @width: Expected link width (%1 or %2) + * @timeout_msec: Timeout in ms how long to wait + * + * Should be used after both ends of the link have been bonded (or + * bonding has been disabled) to wait until the link actually reaches + * the expected state. Returns %-ETIMEDOUT if the @width was not reached + * within the given timeout, %0 if it did. + */ +int tb_port_wait_for_link_width(struct tb_port *port, int width, + int timeout_msec) +{ + ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec); + int ret; + + do { + ret = tb_port_get_link_width(port); + if (ret < 0) + return ret; + else if (ret == width) + return 0; + + usleep_range(1000, 2000); + } while (ktime_before(ktime_get(), timeout)); + + return -ETIMEDOUT; +} + static int tb_port_start_lane_initialization(struct tb_port *port) { int ret; @@ -2432,6 +2465,12 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) return ret; } + ret = tb_port_wait_for_link_width(down, 2, 100); + if (ret) { + tb_port_warn(down, "timeout enabling lane bonding\n"); + return ret; + } + tb_switch_update_link_attributes(sw); tb_sw_dbg(sw, "lane bonding enabled\n"); @@ -2462,6 +2501,13 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw) tb_port_lane_bonding_disable(up); tb_port_lane_bonding_disable(down); + /* + * It is fine if we get other errors as the router might have + * been unplugged. + */ + if (tb_port_wait_for_link_width(down, 1, 100) == -ETIMEDOUT) + tb_sw_warn(sw, "timeout disabling lane bonding\n"); + tb_switch_update_link_attributes(sw); tb_sw_dbg(sw, "lane bonding disabled\n"); } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index b4bc25b82fdb..e6c5e8fc7de7 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -883,6 +883,8 @@ int tb_port_get_link_width(struct tb_port *port); int tb_port_state(struct tb_port *port); int tb_port_lane_bonding_enable(struct tb_port *port); void tb_port_lane_bonding_disable(struct tb_port *port); +int tb_port_wait_for_link_width(struct tb_port *port, int width, + int timeout_msec); 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); diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index b21d99d59412..39c2da112238 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -1527,6 +1527,12 @@ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) return ret; } + ret = tb_port_wait_for_link_width(port, 2, 100); + if (ret) { + tb_port_warn(port, "timeout enabling lane bonding\n"); + return ret; + } + tb_xdomain_update_link_attributes(xd); dev_dbg(&xd->dev, "lane bonding enabled\n"); @@ -1548,6 +1554,8 @@ void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd) port = tb_port_at(xd->route, tb_xdomain_parent(xd)); if (port->dual_link_port) { tb_port_lane_bonding_disable(port); + if (tb_port_wait_for_link_width(port, 1, 100) == -ETIMEDOUT) + tb_port_warn(port, "timeout disabling lane bonding\n"); tb_port_disable(port->dual_link_port); tb_xdomain_update_link_attributes(xd); -- cgit v1.2.3 From 56ad3aef5cdac0695944985f7f70209aec0efd4d Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 10 Mar 2021 13:34:12 +0200 Subject: thunderbolt: Read router preferred credit allocation information USB4 routers must expose their preferred credit (buffer) allocation information through router operation. This information tells the connection manager how the router prefers its buffers to be allocated to get the expected bandwidth for the supported protocols. Read this information and store it as part of struct tb_switch for each USB4 router. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 51 +++++++++++--- drivers/thunderbolt/tb.h | 22 ++++++ drivers/thunderbolt/tb_regs.h | 1 + drivers/thunderbolt/usb4.c | 155 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+), 8 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index d5420eefe25d..ac6cb304c49f 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -488,17 +488,21 @@ static const char *tb_port_type(const struct tb_regs_port_header *port) } } -static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) +static void tb_dump_port(struct tb *tb, const struct tb_port *port) { + const struct tb_regs_port_header *regs = &port->config; + tb_dbg(tb, " Port %d: %x:%x (Revision: %d, TB Version: %d, Type: %s (%#x))\n", - port->port_number, port->vendor_id, port->device_id, - port->revision, port->thunderbolt_version, tb_port_type(port), - port->type); + regs->port_number, regs->vendor_id, regs->device_id, + regs->revision, regs->thunderbolt_version, tb_port_type(regs), + regs->type); tb_dbg(tb, " Max hop id (in/out): %d/%d\n", - port->max_in_hop_id, port->max_out_hop_id); - tb_dbg(tb, " Max counters: %d\n", port->max_counters); - tb_dbg(tb, " NFC Credits: %#x\n", port->nfc_credits); + regs->max_in_hop_id, regs->max_out_hop_id); + tb_dbg(tb, " Max counters: %d\n", regs->max_counters); + tb_dbg(tb, " NFC Credits: %#x\n", regs->nfc_credits); + tb_dbg(tb, " Credits (total/control): %u/%u\n", port->total_credits, + port->ctl_credits); } /** @@ -738,13 +742,32 @@ static int tb_init_port(struct tb_port *port) cap = tb_port_find_cap(port, TB_PORT_CAP_USB4); if (cap > 0) port->cap_usb4 = cap; + + /* + * USB4 ports the buffers allocated for the control path + * can be read from the path config space. Legacy + * devices we use hard-coded value. + */ + if (tb_switch_is_usb4(port->sw)) { + struct tb_regs_hop hop; + + if (!tb_port_read(port, &hop, TB_CFG_HOPS, 0, 2)) + port->ctl_credits = hop.initial_credits; + } + if (!port->ctl_credits) + port->ctl_credits = 2; + } else if (port->port != 0) { cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP); if (cap > 0) port->cap_adap = cap; } - tb_dump_port(port->sw->tb, &port->config); + port->total_credits = + (port->config.nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >> + ADP_CS_4_TOTAL_BUFFERS_SHIFT; + + tb_dump_port(port->sw->tb, port); INIT_LIST_HEAD(&port->list); return 0; @@ -2575,6 +2598,16 @@ void tb_switch_unconfigure_link(struct tb_switch *sw) tb_lc_unconfigure_port(down); } +static void tb_switch_credits_init(struct tb_switch *sw) +{ + if (tb_switch_is_icm(sw)) + return; + if (!tb_switch_is_usb4(sw)) + return; + if (usb4_switch_credits_init(sw)) + tb_sw_info(sw, "failed to determine preferred buffer allocation, using defaults\n"); +} + /** * tb_switch_add() - Add a switch to the domain * @sw: Switch to add @@ -2605,6 +2638,8 @@ int tb_switch_add(struct tb_switch *sw) } if (!sw->safe_mode) { + tb_switch_credits_init(sw); + /* read drom */ ret = tb_drom_read(sw); if (ret) { diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index e6c5e8fc7de7..a8190009815c 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -136,6 +136,12 @@ struct tb_switch_tmu { * @rpm_complete: Completion used to wait for runtime resume to * complete (ICM only) * @quirks: Quirks used for this Thunderbolt switch + * @credit_allocation: Are the below buffer allocation parameters valid + * @max_usb3_credits: Router preferred number of buffers for USB 3.x + * @min_dp_aux_credits: Router preferred minimum number of buffers for DP AUX + * @min_dp_main_credits: Router preferred minimum number of buffers for DP MAIN + * @max_pcie_credits: Router preferred number of buffers for PCIe + * @max_dma_credits: Router preferred number of buffers for DMA/P2P * * When the switch is being added or removed to the domain (other * switches) you need to have domain lock held. @@ -178,6 +184,12 @@ struct tb_switch { u8 depth; struct completion rpm_complete; unsigned long quirks; + bool credit_allocation; + unsigned int max_usb3_credits; + unsigned int min_dp_aux_credits; + unsigned int min_dp_main_credits; + unsigned int max_pcie_credits; + unsigned int max_dma_credits; }; /** @@ -199,6 +211,8 @@ struct tb_switch { * @in_hopids: Currently allocated input HopIDs * @out_hopids: Currently allocated output HopIDs * @list: Used to link ports to DP resources list + * @total_credits: Total number of buffers available for this port + * @ctl_credits: Buffers reserved for control path * * In USB4 terminology this structure represents an adapter (protocol or * lane adapter). @@ -220,6 +234,8 @@ struct tb_port { struct ida in_hopids; struct ida out_hopids; struct list_head list; + unsigned int total_credits; + unsigned int ctl_credits; }; /** @@ -866,6 +882,11 @@ void tb_port_release_out_hopid(struct tb_port *port, int hopid); struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, struct tb_port *prev); +static inline bool tb_port_use_credit_allocation(const struct tb_port *port) +{ + return tb_port_is_null(port) && port->sw->credit_allocation; +} + /** * tb_for_each_port_on_path() - Iterate over each port on path * @src: Source port @@ -994,6 +1015,7 @@ 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); int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status); +int usb4_switch_credits_init(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); diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 113d7903b183..484f25be2849 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -229,6 +229,7 @@ enum usb4_switch_op { USB4_SWITCH_OP_NVM_SET_OFFSET = 0x23, USB4_SWITCH_OP_DROM_READ = 0x24, USB4_SWITCH_OP_NVM_SECTOR_SIZE = 0x25, + USB4_SWITCH_OP_BUFFER_ALLOC = 0x33, }; /* Router TMU configuration */ diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index b56af7b0a093..edab8ea63c0b 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -36,6 +36,20 @@ enum usb4_sb_target { #define USB4_NVM_SECTOR_SIZE_MASK GENMASK(23, 0) +#define USB4_BA_LENGTH_MASK GENMASK(7, 0) +#define USB4_BA_INDEX_MASK GENMASK(15, 0) + +enum usb4_ba_index { + USB4_BA_MAX_USB3 = 0x1, + USB4_BA_MIN_DP_AUX = 0x2, + USB4_BA_MIN_DP_MAIN = 0x3, + USB4_BA_MAX_PCIE = 0x4, + USB4_BA_MAX_HI = 0x5, +}; + +#define USB4_BA_VALUE_MASK GENMASK(31, 16) +#define USB4_BA_VALUE_SHIFT 16 + static int usb4_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, u32 value, int timeout_msec) { @@ -669,6 +683,147 @@ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) return 0; } +/** + * usb4_switch_credits_init() - Read buffer allocation parameters + * @sw: USB4 router + * + * Reads @sw buffer allocation parameters and initializes @sw buffer + * allocation fields accordingly. Specifically @sw->credits_allocation + * is set to %true if these parameters can be used in tunneling. + * + * Returns %0 on success and negative errno otherwise. + */ +int usb4_switch_credits_init(struct tb_switch *sw) +{ + int max_usb3, min_dp_aux, min_dp_main, max_pcie, max_dma; + int ret, length, i, nports; + const struct tb_port *port; + u32 data[NVM_DATA_DWORDS]; + u32 metadata = 0; + u8 status = 0; + + memset(data, 0, sizeof(data)); + ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_BUFFER_ALLOC, &metadata, + &status, NULL, 0, data, ARRAY_SIZE(data)); + if (ret) + return ret; + if (status) + return -EIO; + + length = metadata & USB4_BA_LENGTH_MASK; + if (WARN_ON(length > ARRAY_SIZE(data))) + return -EMSGSIZE; + + max_usb3 = -1; + min_dp_aux = -1; + min_dp_main = -1; + max_pcie = -1; + max_dma = -1; + + tb_sw_dbg(sw, "credit allocation parameters:\n"); + + for (i = 0; i < length; i++) { + u16 index, value; + + index = data[i] & USB4_BA_INDEX_MASK; + value = (data[i] & USB4_BA_VALUE_MASK) >> USB4_BA_VALUE_SHIFT; + + switch (index) { + case USB4_BA_MAX_USB3: + tb_sw_dbg(sw, " USB3: %u\n", value); + max_usb3 = value; + break; + case USB4_BA_MIN_DP_AUX: + tb_sw_dbg(sw, " DP AUX: %u\n", value); + min_dp_aux = value; + break; + case USB4_BA_MIN_DP_MAIN: + tb_sw_dbg(sw, " DP main: %u\n", value); + min_dp_main = value; + break; + case USB4_BA_MAX_PCIE: + tb_sw_dbg(sw, " PCIe: %u\n", value); + max_pcie = value; + break; + case USB4_BA_MAX_HI: + tb_sw_dbg(sw, " DMA: %u\n", value); + max_dma = value; + break; + default: + tb_sw_dbg(sw, " unknown credit allocation index %#x, skipping\n", + index); + break; + } + } + + /* + * Validate the buffer allocation preferences. If we find + * issues, log a warning and fall back using the hard-coded + * values. + */ + + /* Host router must report baMaxHI */ + if (!tb_route(sw) && max_dma < 0) { + tb_sw_warn(sw, "host router is missing baMaxHI\n"); + goto err_invalid; + } + + nports = 0; + tb_switch_for_each_port(sw, port) { + if (tb_port_is_null(port)) + nports++; + } + + /* Must have DP buffer allocation (multiple USB4 ports) */ + if (nports > 2 && (min_dp_aux < 0 || min_dp_main < 0)) { + tb_sw_warn(sw, "multiple USB4 ports require baMinDPaux/baMinDPmain\n"); + goto err_invalid; + } + + tb_switch_for_each_port(sw, port) { + if (tb_port_is_dpout(port) && min_dp_main < 0) { + tb_sw_warn(sw, "missing baMinDPmain"); + goto err_invalid; + } + if ((tb_port_is_dpin(port) || tb_port_is_dpout(port)) && + min_dp_aux < 0) { + tb_sw_warn(sw, "missing baMinDPaux"); + goto err_invalid; + } + if ((tb_port_is_usb3_down(port) || tb_port_is_usb3_up(port)) && + max_usb3 < 0) { + tb_sw_warn(sw, "missing baMaxUSB3"); + goto err_invalid; + } + if ((tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) && + max_pcie < 0) { + tb_sw_warn(sw, "missing baMaxPCIe"); + goto err_invalid; + } + } + + /* + * Buffer allocation passed the validation so we can use it in + * path creation. + */ + sw->credit_allocation = true; + if (max_usb3 > 0) + sw->max_usb3_credits = max_usb3; + if (min_dp_aux > 0) + sw->min_dp_aux_credits = min_dp_aux; + if (min_dp_main > 0) + sw->min_dp_main_credits = min_dp_main; + if (max_pcie > 0) + sw->max_pcie_credits = max_pcie; + if (max_dma > 0) + sw->max_dma_credits = max_dma; + + return 0; + +err_invalid: + return -EINVAL; +} + /** * usb4_switch_query_dp_resource() - Query availability of DP IN resource * @sw: USB4 router -- cgit v1.2.3 From 69fea377e660dcd2f5ab3097b9bd5ed29cfac1fa Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 22 Mar 2021 17:01:59 +0200 Subject: thunderbolt: Update port credits after bonding is enabled/disabled Once lane bonding has been enabled (or disabled) both lane adapters may update their total credits accordingly. For this reason re-read the port credits after lane bonding has been enabled or disabled. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 48 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/xdomain.c | 2 ++ 3 files changed, 51 insertions(+) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index ac6cb304c49f..e015dc93a916 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1099,6 +1099,49 @@ int tb_port_wait_for_link_width(struct tb_port *port, int width, return -ETIMEDOUT; } +static int tb_port_do_update_credits(struct tb_port *port) +{ + u32 nfc_credits; + int ret; + + ret = tb_port_read(port, &nfc_credits, TB_CFG_PORT, ADP_CS_4, 1); + if (ret) + return ret; + + if (nfc_credits != port->config.nfc_credits) { + u32 total; + + total = (nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >> + ADP_CS_4_TOTAL_BUFFERS_SHIFT; + + tb_port_dbg(port, "total credits changed %u -> %u\n", + port->total_credits, total); + + port->config.nfc_credits = nfc_credits; + port->total_credits = total; + } + + return 0; +} + +/** + * tb_port_update_credits() - Re-read port total credits + * @port: Port to update + * + * After the link is bonded (or bonding was disabled) the port total + * credits may change, so this function needs to be called to re-read + * the credits. Updates also the second lane adapter. + */ +int tb_port_update_credits(struct tb_port *port) +{ + int ret; + + ret = tb_port_do_update_credits(port); + if (ret) + return ret; + return tb_port_do_update_credits(port->dual_link_port); +} + static int tb_port_start_lane_initialization(struct tb_port *port) { int ret; @@ -2494,6 +2537,8 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) return ret; } + tb_port_update_credits(down); + tb_port_update_credits(up); tb_switch_update_link_attributes(sw); tb_sw_dbg(sw, "lane bonding enabled\n"); @@ -2531,7 +2576,10 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw) if (tb_port_wait_for_link_width(down, 1, 100) == -ETIMEDOUT) tb_sw_warn(sw, "timeout disabling lane bonding\n"); + tb_port_update_credits(down); + tb_port_update_credits(up); tb_switch_update_link_attributes(sw); + tb_sw_dbg(sw, "lane bonding disabled\n"); } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a8190009815c..e2f304d4a65d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -906,6 +906,7 @@ int tb_port_lane_bonding_enable(struct tb_port *port); void tb_port_lane_bonding_disable(struct tb_port *port); int tb_port_wait_for_link_width(struct tb_port *port, int width, int timeout_msec); +int tb_port_update_credits(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); diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 39c2da112238..d66ea4d616fd 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -1533,6 +1533,7 @@ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) return ret; } + tb_port_update_credits(port); tb_xdomain_update_link_attributes(xd); dev_dbg(&xd->dev, "lane bonding enabled\n"); @@ -1557,6 +1558,7 @@ void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd) if (tb_port_wait_for_link_width(port, 1, 100) == -ETIMEDOUT) tb_port_warn(port, "timeout disabling lane bonding\n"); tb_port_disable(port->dual_link_port); + tb_port_update_credits(port); tb_xdomain_update_link_attributes(xd); dev_dbg(&xd->dev, "lane bonding disabled\n"); -- cgit v1.2.3 From 6ed541c53edcd5bf3cbd9fd600fd593e95ec79fb Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 22 Mar 2021 18:09:35 +0200 Subject: thunderbolt: Allocate credits according to router preferences The USB4 Connection Manager guide provides detailed information how the USB4 router buffer (credit) allocation information should be used by the connection manager when it allocates buffers for different paths. This patch implements it for Linux. For USB 3.x and DisplayPort we use directly the router preferences. The rest of the buffer space is then used for PCIe and DMA (peer-to-peer, XDomain) traffic. DMA tunnels require at least one buffer and PCIe six, so if there is not enough buffers we fail the tunnel creation. For the legacy Thunderbolt 1-3 devices we use the existing hard-coded scheme except for DMA where we use the values suggested by the USB4 spec chapter 13. Co-developed-by: Gil Fine Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 14 ++ drivers/thunderbolt/tunnel.c | 404 +++++++++++++++++++++++++++++++++++-------- drivers/thunderbolt/tunnel.h | 2 + 3 files changed, 346 insertions(+), 74 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index e2f304d4a65d..89e38aeea52b 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -213,6 +213,8 @@ struct tb_switch { * @list: Used to link ports to DP resources list * @total_credits: Total number of buffers available for this port * @ctl_credits: Buffers reserved for control path + * @dma_credits: Number of credits allocated for DMA tunneling for all + * DMA paths through this port. * * In USB4 terminology this structure represents an adapter (protocol or * lane adapter). @@ -236,6 +238,7 @@ struct tb_port { struct list_head list; unsigned int total_credits; unsigned int ctl_credits; + unsigned int dma_credits; }; /** @@ -941,6 +944,17 @@ bool tb_path_is_invalid(struct tb_path *path); bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port); +/** + * tb_path_for_each_hop() - Iterate over each hop on path + * @path: Path whose hops to iterate + * @hop: Hop used as iterator + * + * Iterates over each hop on path. + */ +#define tb_path_for_each_hop(path, hop) \ + for ((hop) = &(path)->hops[0]; \ + (hop) <= &(path)->hops[(path)->path_length - 1]; (hop)++) + int tb_drom_read(struct tb_switch *sw); int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid); diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 5be0f31949f1..bb5cc480fc9a 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -34,6 +34,16 @@ #define TB_DP_AUX_PATH_OUT 1 #define TB_DP_AUX_PATH_IN 2 +/* Minimum number of credits needed for PCIe path */ +#define TB_MIN_PCIE_CREDITS 6U +/* + * Number of credits we try to allocate for each DMA path if not limited + * by the host router baMaxHI. + */ +#define TB_DMA_CREDITS 14U +/* Minimum number of credits for DMA path */ +#define TB_MIN_DMA_CREDITS 1U + static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" }; #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \ @@ -57,6 +67,55 @@ static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" }; #define tb_tunnel_dbg(tunnel, fmt, arg...) \ __TB_TUNNEL_PRINT(tb_dbg, tunnel, fmt, ##arg) +static inline unsigned int tb_usable_credits(const struct tb_port *port) +{ + return port->total_credits - port->ctl_credits; +} + +/** + * tb_available_credits() - Available credits for PCIe and DMA + * @port: Lane adapter to check + * @max_dp_streams: If non-%NULL stores maximum number of simultaneous DP + * streams possible through this lane adapter + */ +static unsigned int tb_available_credits(const struct tb_port *port, + size_t *max_dp_streams) +{ + const struct tb_switch *sw = port->sw; + int credits, usb3, pcie, spare; + size_t ndp; + + usb3 = tb_acpi_may_tunnel_usb3() ? sw->max_usb3_credits : 0; + pcie = tb_acpi_may_tunnel_pcie() ? sw->max_pcie_credits : 0; + + if (tb_acpi_is_xdomain_allowed()) { + spare = min_not_zero(sw->max_dma_credits, TB_DMA_CREDITS); + /* Add some credits for potential second DMA tunnel */ + spare += TB_MIN_DMA_CREDITS; + } else { + spare = 0; + } + + credits = tb_usable_credits(port); + if (tb_acpi_may_tunnel_dp()) { + /* + * Maximum number of DP streams possible through the + * lane adapter. + */ + ndp = (credits - (usb3 + pcie + spare)) / + (sw->min_dp_aux_credits + sw->min_dp_main_credits); + } else { + ndp = 0; + } + credits -= ndp * (sw->min_dp_aux_credits + sw->min_dp_main_credits); + credits -= usb3; + + if (max_dp_streams) + *max_dp_streams = ndp; + + return credits > 0 ? credits : 0; +} + static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths, enum tb_tunnel_type type) { @@ -94,24 +153,37 @@ static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) return 0; } -static int tb_initial_credits(const struct tb_switch *sw) +static int tb_pci_init_credits(struct tb_path_hop *hop) { - /* If the path is complete sw is not NULL */ - if (sw) { - /* More credits for faster link */ - switch (sw->link_speed * sw->link_width) { - case 40: - return 32; - case 20: - return 24; - } + struct tb_port *port = hop->in_port; + struct tb_switch *sw = port->sw; + unsigned int credits; + + if (tb_port_use_credit_allocation(port)) { + unsigned int available; + + available = tb_available_credits(port, NULL); + credits = min(sw->max_pcie_credits, available); + + if (credits < TB_MIN_PCIE_CREDITS) + return -ENOSPC; + + credits = max(TB_MIN_PCIE_CREDITS, credits); + } else { + if (tb_port_is_null(port)) + credits = port->bonded ? 32 : 16; + else + credits = 7; } - return 16; + hop->initial_credits = credits; + return 0; } -static void tb_pci_init_path(struct tb_path *path) +static int tb_pci_init_path(struct tb_path *path) { + struct tb_path_hop *hop; + path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL; path->egress_shared_buffer = TB_PATH_NONE; path->ingress_fc_enable = TB_PATH_ALL; @@ -119,10 +191,16 @@ static void tb_pci_init_path(struct tb_path *path) path->priority = 3; path->weight = 1; path->drop_packages = 0; - path->hops[0].initial_credits = 7; - if (path->path_length > 1) - path->hops[1].initial_credits = - tb_initial_credits(path->hops[1].in_port->sw); + + tb_path_for_each_hop(path, hop) { + int ret; + + ret = tb_pci_init_credits(hop); + if (ret) + return ret; + } + + return 0; } /** @@ -162,14 +240,16 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down) goto err_free; } tunnel->paths[TB_PCI_PATH_UP] = path; - tb_pci_init_path(tunnel->paths[TB_PCI_PATH_UP]); + if (tb_pci_init_path(tunnel->paths[TB_PCI_PATH_UP])) + goto err_free; path = tb_path_discover(tunnel->dst_port, -1, down, TB_PCI_HOPID, NULL, "PCIe Down"); if (!path) goto err_deactivate; tunnel->paths[TB_PCI_PATH_DOWN] = path; - tb_pci_init_path(tunnel->paths[TB_PCI_PATH_DOWN]); + if (tb_pci_init_path(tunnel->paths[TB_PCI_PATH_DOWN])) + goto err_deactivate; /* Validate that the tunnel is complete */ if (!tb_port_is_pcie_up(tunnel->dst_port)) { @@ -227,23 +307,25 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, path = tb_path_alloc(tb, down, TB_PCI_HOPID, up, TB_PCI_HOPID, 0, "PCIe Down"); - if (!path) { - tb_tunnel_free(tunnel); - return NULL; - } - tb_pci_init_path(path); + if (!path) + goto err_free; tunnel->paths[TB_PCI_PATH_DOWN] = path; + if (tb_pci_init_path(path)) + goto err_free; path = tb_path_alloc(tb, up, TB_PCI_HOPID, down, TB_PCI_HOPID, 0, "PCIe Up"); - if (!path) { - tb_tunnel_free(tunnel); - return NULL; - } - tb_pci_init_path(path); + if (!path) + goto err_free; tunnel->paths[TB_PCI_PATH_UP] = path; + if (tb_pci_init_path(path)) + goto err_free; return tunnel; + +err_free: + tb_tunnel_free(tunnel); + return NULL; } static bool tb_dp_is_usb4(const struct tb_switch *sw) @@ -598,9 +680,20 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, return 0; } +static void tb_dp_init_aux_credits(struct tb_path_hop *hop) +{ + struct tb_port *port = hop->in_port; + struct tb_switch *sw = port->sw; + + if (tb_port_use_credit_allocation(port)) + hop->initial_credits = sw->min_dp_aux_credits; + else + hop->initial_credits = 1; +} + static void tb_dp_init_aux_path(struct tb_path *path) { - int i; + struct tb_path_hop *hop; path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL; path->egress_shared_buffer = TB_PATH_NONE; @@ -609,13 +702,42 @@ static void tb_dp_init_aux_path(struct tb_path *path) path->priority = 2; path->weight = 1; - for (i = 0; i < path->path_length; i++) - path->hops[i].initial_credits = 1; + tb_path_for_each_hop(path, hop) + tb_dp_init_aux_credits(hop); } -static void tb_dp_init_video_path(struct tb_path *path, bool discover) +static int tb_dp_init_video_credits(struct tb_path_hop *hop) { - int i; + struct tb_port *port = hop->in_port; + struct tb_switch *sw = port->sw; + + if (tb_port_use_credit_allocation(port)) { + unsigned int nfc_credits; + size_t max_dp_streams; + + tb_available_credits(port, &max_dp_streams); + /* + * Read the number of currently allocated NFC credits + * from the lane adapter. Since we only use them for DP + * tunneling we can use that to figure out how many DP + * tunnels already go through the lane adapter. + */ + nfc_credits = port->config.nfc_credits & + ADP_CS_4_NFC_BUFFERS_MASK; + if (nfc_credits / sw->min_dp_main_credits > max_dp_streams) + return -ENOSPC; + + hop->nfc_credits = sw->min_dp_main_credits; + } else { + hop->nfc_credits = min(port->total_credits - 2, 12U); + } + + return 0; +} + +static int tb_dp_init_video_path(struct tb_path *path) +{ + struct tb_path_hop *hop; path->egress_fc_enable = TB_PATH_NONE; path->egress_shared_buffer = TB_PATH_NONE; @@ -624,21 +746,15 @@ static void tb_dp_init_video_path(struct tb_path *path, bool discover) path->priority = 1; path->weight = 1; - for (i = 0; i < path->path_length; i++) { - u32 nfc_credits = path->hops[i].in_port->config.nfc_credits; - - if (discover) { - path->hops[i].nfc_credits = - nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK; - } else { - u32 max_credits; + tb_path_for_each_hop(path, hop) { + int ret; - max_credits = (nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >> - ADP_CS_4_TOTAL_BUFFERS_SHIFT; - /* Leave some credits for AUX path */ - path->hops[i].nfc_credits = min(max_credits - 2, 12U); - } + ret = tb_dp_init_video_credits(hop); + if (ret) + return ret; } + + return 0; } /** @@ -678,7 +794,8 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in) goto err_free; } tunnel->paths[TB_DP_VIDEO_PATH_OUT] = path; - tb_dp_init_video_path(tunnel->paths[TB_DP_VIDEO_PATH_OUT], true); + if (tb_dp_init_video_path(tunnel->paths[TB_DP_VIDEO_PATH_OUT])) + goto err_free; path = tb_path_discover(in, TB_DP_AUX_TX_HOPID, NULL, -1, NULL, "AUX TX"); if (!path) @@ -765,7 +882,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, 1, "Video"); if (!path) goto err_free; - tb_dp_init_video_path(path, false); + tb_dp_init_video_path(path); paths[TB_DP_VIDEO_PATH_OUT] = path; path = tb_path_alloc(tb, in, TB_DP_AUX_TX_HOPID, out, @@ -789,20 +906,58 @@ err_free: return NULL; } -static u32 tb_dma_credits(struct tb_port *nhi) +static unsigned int tb_dma_available_credits(const struct tb_port *port) { - u32 max_credits; + const struct tb_switch *sw = port->sw; + int credits; + + credits = tb_available_credits(port, NULL); + if (tb_acpi_may_tunnel_pcie()) + credits -= sw->max_pcie_credits; + credits -= port->dma_credits; - max_credits = (nhi->config.nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >> - ADP_CS_4_TOTAL_BUFFERS_SHIFT; - return min(max_credits, 13U); + return credits > 0 ? credits : 0; } -static void tb_dma_init_path(struct tb_path *path, unsigned int efc, u32 credits) +static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) { - int i; + struct tb_port *port = hop->in_port; + + if (tb_port_use_credit_allocation(port)) { + unsigned int available = tb_dma_available_credits(port); + + /* + * Need to have at least TB_MIN_DMA_CREDITS, otherwise + * DMA path cannot be established. + */ + if (available < TB_MIN_DMA_CREDITS) + return -ENOSPC; + + while (credits > available) + credits--; + + tb_port_dbg(port, "reserving %u credits for DMA path\n", + credits); + + port->dma_credits += credits; + } else { + if (tb_port_is_null(port)) + credits = port->bonded ? 14 : 6; + else + credits = min(port->total_credits, credits); + } + + hop->initial_credits = credits; + return 0; +} + +/* Path from lane adapter to NHI */ +static int tb_dma_init_rx_path(struct tb_path *path, unsigned int credits) +{ + struct tb_path_hop *hop; + unsigned int i, tmp; - path->egress_fc_enable = efc; + path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL; path->ingress_fc_enable = TB_PATH_ALL; path->egress_shared_buffer = TB_PATH_NONE; path->ingress_shared_buffer = TB_PATH_NONE; @@ -810,8 +965,80 @@ static void tb_dma_init_path(struct tb_path *path, unsigned int efc, u32 credits path->weight = 1; path->clear_fc = true; - for (i = 0; i < path->path_length; i++) - path->hops[i].initial_credits = credits; + /* + * First lane adapter is the one connected to the remote host. + * We don't tunnel other traffic over this link so can use all + * the credits (except the ones reserved for control traffic). + */ + hop = &path->hops[0]; + tmp = min(tb_usable_credits(hop->in_port), credits); + hop->initial_credits = tmp; + hop->in_port->dma_credits += tmp; + + for (i = 1; i < path->path_length; i++) { + int ret; + + ret = tb_dma_reserve_credits(&path->hops[i], credits); + if (ret) + return ret; + } + + return 0; +} + +/* Path from NHI to lane adapter */ +static int tb_dma_init_tx_path(struct tb_path *path, unsigned int credits) +{ + struct tb_path_hop *hop; + + path->egress_fc_enable = TB_PATH_ALL; + path->ingress_fc_enable = TB_PATH_ALL; + path->egress_shared_buffer = TB_PATH_NONE; + path->ingress_shared_buffer = TB_PATH_NONE; + path->priority = 5; + path->weight = 1; + path->clear_fc = true; + + tb_path_for_each_hop(path, hop) { + int ret; + + ret = tb_dma_reserve_credits(hop, credits); + if (ret) + return ret; + } + + return 0; +} + +static void tb_dma_release_credits(struct tb_path_hop *hop) +{ + struct tb_port *port = hop->in_port; + + if (tb_port_use_credit_allocation(port)) { + port->dma_credits -= hop->initial_credits; + + tb_port_dbg(port, "released %u DMA path credits\n", + hop->initial_credits); + } +} + +static void tb_dma_deinit_path(struct tb_path *path) +{ + struct tb_path_hop *hop; + + tb_path_for_each_hop(path, hop) + tb_dma_release_credits(hop); +} + +static void tb_dma_deinit(struct tb_tunnel *tunnel) +{ + int i; + + for (i = 0; i < tunnel->npaths; i++) { + if (!tunnel->paths[i]) + continue; + tb_dma_deinit_path(tunnel->paths[i]); + } } /** @@ -836,7 +1063,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_tunnel *tunnel; size_t npaths = 0, i = 0; struct tb_path *path; - u32 credits; + int credits; if (receive_ring > 0) npaths++; @@ -852,32 +1079,39 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, tunnel->src_port = nhi; tunnel->dst_port = dst; + tunnel->deinit = tb_dma_deinit; - credits = tb_dma_credits(nhi); + credits = min_not_zero(TB_DMA_CREDITS, nhi->sw->max_dma_credits); if (receive_ring > 0) { path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, "DMA RX"); - if (!path) { - tb_tunnel_free(tunnel); - return NULL; - } - tb_dma_init_path(path, TB_PATH_SOURCE | TB_PATH_INTERNAL, credits); + if (!path) + goto err_free; tunnel->paths[i++] = path; + if (tb_dma_init_rx_path(path, credits)) { + tb_tunnel_dbg(tunnel, "not enough buffers for RX path\n"); + goto err_free; + } } if (transmit_ring > 0) { path = tb_path_alloc(tb, nhi, transmit_ring, dst, transmit_path, 0, "DMA TX"); - if (!path) { - tb_tunnel_free(tunnel); - return NULL; - } - tb_dma_init_path(path, TB_PATH_ALL, credits); + if (!path) + goto err_free; tunnel->paths[i++] = path; + if (tb_dma_init_tx_path(path, credits)) { + tb_tunnel_dbg(tunnel, "not enough buffers for TX path\n"); + goto err_free; + } } return tunnel; + +err_free: + tb_tunnel_free(tunnel); + return NULL; } /** @@ -1071,8 +1305,28 @@ static void tb_usb3_reclaim_available_bandwidth(struct tb_tunnel *tunnel, tunnel->allocated_up, tunnel->allocated_down); } +static void tb_usb3_init_credits(struct tb_path_hop *hop) +{ + struct tb_port *port = hop->in_port; + struct tb_switch *sw = port->sw; + unsigned int credits; + + if (tb_port_use_credit_allocation(port)) { + credits = sw->max_usb3_credits; + } else { + if (tb_port_is_null(port)) + credits = port->bonded ? 32 : 16; + else + credits = 7; + } + + hop->initial_credits = credits; +} + static void tb_usb3_init_path(struct tb_path *path) { + struct tb_path_hop *hop; + path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL; path->egress_shared_buffer = TB_PATH_NONE; path->ingress_fc_enable = TB_PATH_ALL; @@ -1080,10 +1334,9 @@ static void tb_usb3_init_path(struct tb_path *path) path->priority = 3; path->weight = 3; path->drop_packages = 0; - path->hops[0].initial_credits = 7; - if (path->path_length > 1) - path->hops[1].initial_credits = - tb_initial_credits(path->hops[1].in_port->sw); + + tb_path_for_each_hop(path, hop) + tb_usb3_init_credits(hop); } /** @@ -1283,6 +1536,9 @@ void tb_tunnel_free(struct tb_tunnel *tunnel) if (!tunnel) return; + if (tunnel->deinit) + tunnel->deinit(tunnel); + for (i = 0; i < tunnel->npaths; i++) { if (tunnel->paths[i]) tb_path_free(tunnel->paths[i]); diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index a66994fb4e60..eea14e24f7e0 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -27,6 +27,7 @@ enum tb_tunnel_type { * @paths: All paths required by the tunnel * @npaths: Number of paths in @paths * @init: Optional tunnel specific initialization + * @deinit: Optional tunnel specific de-initialization * @activate: Optional tunnel specific activation/deactivation * @consumed_bandwidth: Return how much bandwidth the tunnel consumes * @release_unused_bandwidth: Release all unused bandwidth @@ -47,6 +48,7 @@ struct tb_tunnel { struct tb_path **paths; size_t npaths; int (*init)(struct tb_tunnel *tunnel); + void (*deinit)(struct tb_tunnel *tunnel); int (*activate)(struct tb_tunnel *tunnel, bool activate); int (*consumed_bandwidth)(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down); -- cgit v1.2.3 From 7c37bb304fd6c085f9630d1740ed53781d4d4821 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 23 Mar 2021 19:05:23 +0200 Subject: thunderbolt: Add quirk for Intel Goshen Ridge DP credits Intel Goshen Ridge reports wrong DP main credits in NVM 27 and earlier, so add a quirk that fixes it. We also need to expand the quirk table to match on hardware vendor/device IDs too. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/quirks.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 892cf0e8ada5..b5f2ec79c4d6 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -12,7 +12,17 @@ static void quirk_force_power_link(struct tb_switch *sw) sw->quirks |= QUIRK_FORCE_POWER_LINK_CONTROLLER; } +static void quirk_dp_credit_allocation(struct tb_switch *sw) +{ + if (sw->credit_allocation && sw->min_dp_main_credits == 56) { + sw->min_dp_main_credits = 18; + tb_sw_dbg(sw, "quirked DP main: %u\n", sw->min_dp_main_credits); + } +} + struct tb_quirk { + u16 hw_vendor_id; + u16 hw_device_id; u16 vendor; u16 device; void (*hook)(struct tb_switch *sw); @@ -20,8 +30,13 @@ struct tb_quirk { static const struct tb_quirk tb_quirks[] = { /* Dell WD19TB supports self-authentication on unplug */ - { 0x00d4, 0xb070, quirk_force_power_link }, - { 0x00d4, 0xb071, quirk_force_power_link }, + { 0x0000, 0x0000, 0x00d4, 0xb070, quirk_force_power_link }, + { 0x0000, 0x0000, 0x00d4, 0xb071, quirk_force_power_link }, + /* + * Intel Goshen Ridge NVM 27 and before report wrong number of + * DP buffers. + */ + { 0x8087, 0x0b26, 0x0000, 0x0000, quirk_dp_credit_allocation }, }; /** @@ -37,7 +52,15 @@ void tb_check_quirks(struct tb_switch *sw) for (i = 0; i < ARRAY_SIZE(tb_quirks); i++) { const struct tb_quirk *q = &tb_quirks[i]; - if (sw->device == q->device && sw->vendor == q->vendor) - q->hook(sw); + if (q->hw_vendor_id && q->hw_vendor_id != sw->config.vendor_id) + continue; + if (q->hw_device_id && q->hw_device_id != sw->config.device_id) + continue; + if (q->vendor && q->vendor != sw->vendor) + continue; + if (q->device && q->device != sw->device) + continue; + + q->hook(sw); } } -- cgit v1.2.3 From bfa8f78e06ed0b495a5736380de0e7f833a5efbe Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 28 Apr 2021 19:04:02 +0300 Subject: thunderbolt: Add KUnit tests for credit allocation This adds a couple of KUnit tests for USB4 credit allocation. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/test.c | 545 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 545 insertions(+) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 5ff5a03bc9ce..cf34c1ecf5d5 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -87,22 +87,30 @@ static struct tb_switch *alloc_host(struct kunit *test) sw->ports[1].config.type = TB_TYPE_PORT; sw->ports[1].config.max_in_hop_id = 19; sw->ports[1].config.max_out_hop_id = 19; + sw->ports[1].total_credits = 60; + sw->ports[1].ctl_credits = 2; sw->ports[1].dual_link_port = &sw->ports[2]; sw->ports[2].config.type = TB_TYPE_PORT; sw->ports[2].config.max_in_hop_id = 19; sw->ports[2].config.max_out_hop_id = 19; + sw->ports[2].total_credits = 60; + sw->ports[2].ctl_credits = 2; sw->ports[2].dual_link_port = &sw->ports[1]; sw->ports[2].link_nr = 1; sw->ports[3].config.type = TB_TYPE_PORT; sw->ports[3].config.max_in_hop_id = 19; sw->ports[3].config.max_out_hop_id = 19; + sw->ports[3].total_credits = 60; + sw->ports[3].ctl_credits = 2; sw->ports[3].dual_link_port = &sw->ports[4]; sw->ports[4].config.type = TB_TYPE_PORT; sw->ports[4].config.max_in_hop_id = 19; sw->ports[4].config.max_out_hop_id = 19; + sw->ports[4].total_credits = 60; + sw->ports[4].ctl_credits = 2; sw->ports[4].dual_link_port = &sw->ports[3]; sw->ports[4].link_nr = 1; @@ -143,6 +151,25 @@ static struct tb_switch *alloc_host(struct kunit *test) return sw; } +static struct tb_switch *alloc_host_usb4(struct kunit *test) +{ + struct tb_switch *sw; + + sw = alloc_host(test); + if (!sw) + return NULL; + + sw->generation = 4; + sw->credit_allocation = true; + sw->max_usb3_credits = 32; + sw->min_dp_aux_credits = 1; + sw->min_dp_main_credits = 0; + sw->max_pcie_credits = 64; + sw->max_dma_credits = 14; + + return sw; +} + static struct tb_switch *alloc_dev_default(struct kunit *test, struct tb_switch *parent, u64 route, bool bonded) @@ -164,44 +191,60 @@ static struct tb_switch *alloc_dev_default(struct kunit *test, sw->ports[1].config.type = TB_TYPE_PORT; sw->ports[1].config.max_in_hop_id = 19; sw->ports[1].config.max_out_hop_id = 19; + sw->ports[1].total_credits = 60; + sw->ports[1].ctl_credits = 2; sw->ports[1].dual_link_port = &sw->ports[2]; sw->ports[2].config.type = TB_TYPE_PORT; sw->ports[2].config.max_in_hop_id = 19; sw->ports[2].config.max_out_hop_id = 19; + sw->ports[2].total_credits = 60; + sw->ports[2].ctl_credits = 2; sw->ports[2].dual_link_port = &sw->ports[1]; sw->ports[2].link_nr = 1; sw->ports[3].config.type = TB_TYPE_PORT; sw->ports[3].config.max_in_hop_id = 19; sw->ports[3].config.max_out_hop_id = 19; + sw->ports[3].total_credits = 60; + sw->ports[3].ctl_credits = 2; sw->ports[3].dual_link_port = &sw->ports[4]; sw->ports[4].config.type = TB_TYPE_PORT; sw->ports[4].config.max_in_hop_id = 19; sw->ports[4].config.max_out_hop_id = 19; + sw->ports[4].total_credits = 60; + sw->ports[4].ctl_credits = 2; sw->ports[4].dual_link_port = &sw->ports[3]; sw->ports[4].link_nr = 1; sw->ports[5].config.type = TB_TYPE_PORT; sw->ports[5].config.max_in_hop_id = 19; sw->ports[5].config.max_out_hop_id = 19; + sw->ports[5].total_credits = 60; + sw->ports[5].ctl_credits = 2; sw->ports[5].dual_link_port = &sw->ports[6]; sw->ports[6].config.type = TB_TYPE_PORT; sw->ports[6].config.max_in_hop_id = 19; sw->ports[6].config.max_out_hop_id = 19; + sw->ports[6].total_credits = 60; + sw->ports[6].ctl_credits = 2; sw->ports[6].dual_link_port = &sw->ports[5]; sw->ports[6].link_nr = 1; sw->ports[7].config.type = TB_TYPE_PORT; sw->ports[7].config.max_in_hop_id = 19; sw->ports[7].config.max_out_hop_id = 19; + sw->ports[7].total_credits = 60; + sw->ports[7].ctl_credits = 2; sw->ports[7].dual_link_port = &sw->ports[8]; sw->ports[8].config.type = TB_TYPE_PORT; sw->ports[8].config.max_in_hop_id = 19; sw->ports[8].config.max_out_hop_id = 19; + sw->ports[8].total_credits = 60; + sw->ports[8].ctl_credits = 2; sw->ports[8].dual_link_port = &sw->ports[7]; sw->ports[8].link_nr = 1; @@ -265,9 +308,13 @@ static struct tb_switch *alloc_dev_default(struct kunit *test, if (bonded) { /* Bonding is used */ port->bonded = true; + port->total_credits *= 2; port->dual_link_port->bonded = true; + port->dual_link_port->total_credits = 0; upstream_port->bonded = true; + upstream_port->total_credits *= 2; upstream_port->dual_link_port->bonded = true; + upstream_port->dual_link_port->total_credits = 0; } return sw; @@ -294,6 +341,27 @@ static struct tb_switch *alloc_dev_with_dpin(struct kunit *test, return sw; } +static struct tb_switch *alloc_dev_usb4(struct kunit *test, + struct tb_switch *parent, + u64 route, bool bonded) +{ + struct tb_switch *sw; + + sw = alloc_dev_default(test, parent, route, bonded); + if (!sw) + return NULL; + + sw->generation = 4; + sw->credit_allocation = true; + sw->max_usb3_credits = 14; + sw->min_dp_aux_credits = 1; + sw->min_dp_main_credits = 18; + sw->max_pcie_credits = 32; + sw->max_dma_credits = 14; + + return sw; +} + static void tb_test_path_basic(struct kunit *test) { struct tb_port *src_port, *dst_port, *p; @@ -1829,6 +1897,475 @@ static void tb_test_tunnel_dma_match(struct kunit *test) tb_tunnel_free(tunnel); } +static void tb_test_credit_alloc_legacy_not_bonded(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *up, *down; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host(test); + dev = alloc_dev_default(test, host, 0x1, false); + + down = &host->ports[8]; + up = &dev->ports[9]; + tunnel = tb_tunnel_alloc_pci(NULL, up, down); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2); + + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 16U); + + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 16U); + + tb_tunnel_free(tunnel); +} + +static void tb_test_credit_alloc_legacy_bonded(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *up, *down; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host(test); + dev = alloc_dev_default(test, host, 0x1, true); + + down = &host->ports[8]; + up = &dev->ports[9]; + tunnel = tb_tunnel_alloc_pci(NULL, up, down); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2); + + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 32U); + + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 32U); + + tb_tunnel_free(tunnel); +} + +static void tb_test_credit_alloc_pcie(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *up, *down; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host_usb4(test); + dev = alloc_dev_usb4(test, host, 0x1, true); + + down = &host->ports[8]; + up = &dev->ports[9]; + tunnel = tb_tunnel_alloc_pci(NULL, up, down); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2); + + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 32U); + + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 64U); + + tb_tunnel_free(tunnel); +} + +static void tb_test_credit_alloc_dp(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *in, *out; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host_usb4(test); + dev = alloc_dev_usb4(test, host, 0x1, true); + + in = &host->ports[5]; + out = &dev->ports[14]; + + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)3); + + /* Video (main) path */ + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 12U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 18U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 0U); + + /* AUX TX */ + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 1U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + /* AUX RX */ + path = tunnel->paths[2]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 1U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + tb_tunnel_free(tunnel); +} + +static void tb_test_credit_alloc_usb3(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *up, *down; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host_usb4(test); + dev = alloc_dev_usb4(test, host, 0x1, true); + + down = &host->ports[12]; + up = &dev->ports[16]; + tunnel = tb_tunnel_alloc_usb3(NULL, up, down, 0, 0); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2); + + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 32U); + + tb_tunnel_free(tunnel); +} + +static void tb_test_credit_alloc_dma(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *nhi, *port; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host_usb4(test); + dev = alloc_dev_usb4(test, host, 0x1, true); + + nhi = &host->ports[7]; + port = &dev->ports[3]; + + tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2); + + /* DMA RX */ + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 14U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + /* DMA TX */ + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + tb_tunnel_free(tunnel); +} + +static void tb_test_credit_alloc_dma_multiple(struct kunit *test) +{ + struct tb_tunnel *tunnel1, *tunnel2, *tunnel3; + struct tb_switch *host, *dev; + struct tb_port *nhi, *port; + struct tb_path *path; + + host = alloc_host_usb4(test); + dev = alloc_dev_usb4(test, host, 0x1, true); + + nhi = &host->ports[7]; + port = &dev->ports[3]; + + /* + * Create three DMA tunnels through the same ports. With the + * default buffers we should be able to create two and the last + * one fails. + * + * For default host we have following buffers for DMA: + * + * 120 - (2 + 2 * (1 + 0) + 32 + 64 + spare) = 20 + * + * For device we have following: + * + * 120 - (2 + 2 * (1 + 18) + 14 + 32 + spare) = 34 + * + * spare = 14 + 1 = 15 + * + * So on host the first tunnel gets 14 and the second gets the + * remaining 1 and then we run out of buffers. + */ + tunnel1 = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1); + KUNIT_ASSERT_TRUE(test, tunnel1 != NULL); + KUNIT_ASSERT_EQ(test, tunnel1->npaths, (size_t)2); + + path = tunnel1->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 14U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + path = tunnel1->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + tunnel2 = tb_tunnel_alloc_dma(NULL, nhi, port, 9, 2, 9, 2); + KUNIT_ASSERT_TRUE(test, tunnel2 != NULL); + KUNIT_ASSERT_EQ(test, tunnel2->npaths, (size_t)2); + + path = tunnel2->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 14U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + path = tunnel2->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + tunnel3 = tb_tunnel_alloc_dma(NULL, nhi, port, 10, 3, 10, 3); + KUNIT_ASSERT_TRUE(test, tunnel3 == NULL); + + /* + * Release the first DMA tunnel. That should make 14 buffers + * available for the next tunnel. + */ + tb_tunnel_free(tunnel1); + + tunnel3 = tb_tunnel_alloc_dma(NULL, nhi, port, 10, 3, 10, 3); + KUNIT_ASSERT_TRUE(test, tunnel3 != NULL); + + path = tunnel3->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 14U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + path = tunnel3->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + tb_tunnel_free(tunnel3); + tb_tunnel_free(tunnel2); +} + +static void tb_test_credit_alloc_all(struct kunit *test) +{ + struct tb_port *up, *down, *in, *out, *nhi, *port; + struct tb_tunnel *pcie_tunnel, *dp_tunnel1, *dp_tunnel2, *usb3_tunnel; + struct tb_tunnel *dma_tunnel1, *dma_tunnel2; + struct tb_switch *host, *dev; + struct tb_path *path; + + /* + * Create PCIe, 2 x DP, USB 3.x and two DMA tunnels from host to + * device. Expectation is that all these can be established with + * the default credit allocation found in Intel hardware. + */ + + host = alloc_host_usb4(test); + dev = alloc_dev_usb4(test, host, 0x1, true); + + down = &host->ports[8]; + up = &dev->ports[9]; + pcie_tunnel = tb_tunnel_alloc_pci(NULL, up, down); + KUNIT_ASSERT_TRUE(test, pcie_tunnel != NULL); + KUNIT_ASSERT_EQ(test, pcie_tunnel->npaths, (size_t)2); + + path = pcie_tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 32U); + + path = pcie_tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 64U); + + in = &host->ports[5]; + out = &dev->ports[13]; + + dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + KUNIT_ASSERT_TRUE(test, dp_tunnel1 != NULL); + KUNIT_ASSERT_EQ(test, dp_tunnel1->npaths, (size_t)3); + + path = dp_tunnel1->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 12U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 18U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 0U); + + path = dp_tunnel1->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 1U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + path = dp_tunnel1->paths[2]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 1U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + in = &host->ports[6]; + out = &dev->ports[14]; + + dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + KUNIT_ASSERT_TRUE(test, dp_tunnel2 != NULL); + KUNIT_ASSERT_EQ(test, dp_tunnel2->npaths, (size_t)3); + + path = dp_tunnel2->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 12U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 18U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 0U); + + path = dp_tunnel2->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 1U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + path = dp_tunnel2->paths[2]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 1U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + down = &host->ports[12]; + up = &dev->ports[16]; + usb3_tunnel = tb_tunnel_alloc_usb3(NULL, up, down, 0, 0); + KUNIT_ASSERT_TRUE(test, usb3_tunnel != NULL); + KUNIT_ASSERT_EQ(test, usb3_tunnel->npaths, (size_t)2); + + path = usb3_tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + path = usb3_tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 32U); + + nhi = &host->ports[7]; + port = &dev->ports[3]; + + dma_tunnel1 = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1); + KUNIT_ASSERT_TRUE(test, dma_tunnel1 != NULL); + KUNIT_ASSERT_EQ(test, dma_tunnel1->npaths, (size_t)2); + + path = dma_tunnel1->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 14U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + path = dma_tunnel1->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 14U); + + dma_tunnel2 = tb_tunnel_alloc_dma(NULL, nhi, port, 9, 2, 9, 2); + KUNIT_ASSERT_TRUE(test, dma_tunnel2 != NULL); + KUNIT_ASSERT_EQ(test, dma_tunnel2->npaths, (size_t)2); + + path = dma_tunnel2->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 14U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + path = dma_tunnel2->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 1U); + + tb_tunnel_free(dma_tunnel2); + tb_tunnel_free(dma_tunnel1); + tb_tunnel_free(usb3_tunnel); + tb_tunnel_free(dp_tunnel2); + tb_tunnel_free(dp_tunnel1); + tb_tunnel_free(pcie_tunnel); +} + static const u32 root_directory[] = { 0x55584401, /* "UXD" v1 */ 0x00000018, /* Root directory length */ @@ -2105,6 +2642,14 @@ static struct kunit_case tb_test_cases[] = { KUNIT_CASE(tb_test_tunnel_dma_tx), KUNIT_CASE(tb_test_tunnel_dma_chain), KUNIT_CASE(tb_test_tunnel_dma_match), + KUNIT_CASE(tb_test_credit_alloc_legacy_not_bonded), + KUNIT_CASE(tb_test_credit_alloc_legacy_bonded), + KUNIT_CASE(tb_test_credit_alloc_pcie), + KUNIT_CASE(tb_test_credit_alloc_dp), + KUNIT_CASE(tb_test_credit_alloc_usb3), + KUNIT_CASE(tb_test_credit_alloc_dma), + KUNIT_CASE(tb_test_credit_alloc_dma_multiple), + KUNIT_CASE(tb_test_credit_alloc_all), KUNIT_CASE(tb_test_property_parse), KUNIT_CASE(tb_test_property_format), KUNIT_CASE(tb_test_property_copy), -- cgit v1.2.3 From 0f28879cf6836f170773a9456c856e1f08f56764 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 1 Apr 2021 20:16:25 +0300 Subject: thunderbolt: Log the link as TBT instead of TBT3 The upstream port can be connected to any previous generation Thunderbolt port so logging as "TBT" is more accurate than "TBT3. No functional changes. Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/usb4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index edab8ea63c0b..94cc25cc6388 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -260,7 +260,7 @@ int usb4_switch_setup(struct tb_switch *sw) parent = tb_switch_parent(sw); downstream_port = tb_port_at(tb_route(sw), parent); sw->link_usb4 = link_is_usb4(downstream_port); - tb_sw_dbg(sw, "link: %s\n", sw->link_usb4 ? "USB4" : "TBT3"); + tb_sw_dbg(sw, "link: %s\n", sw->link_usb4 ? "USB4" : "TBT"); xhci = val & ROUTER_CS_6_HCI; tbt3 = !(val & ROUTER_CS_6_TNS); -- cgit v1.2.3 From cae5f5151d76635f6b5c08133184c48048346e63 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 1 Apr 2021 17:34:20 +0300 Subject: thunderbolt: Add USB4 port devices Create devices for each USB4 port. This is needed when we add retimer access when there is no device connected but may be useful for other purposes too following what USB subsystem does. This exports a single attribute "link" that shows the type of the USB4 link (or "none" if there is no cable connected). Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/retimer.c | 15 ++++-- drivers/thunderbolt/switch.c | 17 +++++- drivers/thunderbolt/tb.h | 30 +++++++++++ drivers/thunderbolt/usb4.c | 54 +++++++++++++++++++ drivers/thunderbolt/usb4_port.c | 112 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 drivers/thunderbolt/usb4_port.c (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 7aa48f6c41d9..da19d7987d00 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -2,7 +2,7 @@ obj-${CONFIG_USB4} := thunderbolt.o thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o -thunderbolt-objs += nvm.o retimer.o quirks.o +thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o thunderbolt-${CONFIG_ACPI} += acpi.o thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index c44fad2b9fbb..2e5188fb1150 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -283,11 +283,13 @@ struct device_type tb_retimer_type = { static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status) { + struct usb4_port *usb4; struct tb_retimer *rt; u32 vendor, device; int ret; - if (!port->cap_usb4) + usb4 = port->usb4; + if (!usb4) return -EINVAL; ret = usb4_port_retimer_read(port, index, USB4_SB_VENDOR_ID, &vendor, @@ -331,7 +333,7 @@ static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status) rt->port = port; rt->tb = port->sw->tb; - rt->dev.parent = &port->sw->dev; + rt->dev.parent = &usb4->dev; rt->dev.bus = &tb_bus_type; rt->dev.type = &tb_retimer_type; dev_set_name(&rt->dev, "%s:%u.%u", dev_name(&port->sw->dev), @@ -389,7 +391,7 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index) struct tb_retimer_lookup lookup = { .port = port, .index = index }; struct device *dev; - dev = device_find_child(&port->sw->dev, &lookup, retimer_match); + dev = device_find_child(&port->usb4->dev, &lookup, retimer_match); if (dev) return tb_to_retimer(dev); @@ -479,7 +481,10 @@ static int remove_retimer(struct device *dev, void *data) */ void tb_retimer_remove_all(struct tb_port *port) { - if (port->cap_usb4) - device_for_each_child_reverse(&port->sw->dev, port, + struct usb4_port *usb4; + + usb4 = port->usb4; + if (usb4) + device_for_each_child_reverse(&usb4->dev, port, remove_retimer); } diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e015dc93a916..7303c61a891a 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2741,11 +2741,16 @@ int tb_switch_add(struct tb_switch *sw) sw->device_name); } + ret = usb4_switch_add_ports(sw); + if (ret) { + dev_err(&sw->dev, "failed to add USB4 ports\n"); + goto err_del; + } + ret = tb_switch_nvm_add(sw); if (ret) { dev_err(&sw->dev, "failed to add NVM devices\n"); - device_del(&sw->dev); - return ret; + goto err_ports; } /* @@ -2766,6 +2771,13 @@ int tb_switch_add(struct tb_switch *sw) tb_switch_debugfs_init(sw); return 0; + +err_ports: + usb4_switch_remove_ports(sw); +err_del: + device_del(&sw->dev); + + return ret; } /** @@ -2805,6 +2817,7 @@ void tb_switch_remove(struct tb_switch *sw) tb_plug_events_active(sw, false); tb_switch_nvm_remove(sw); + usb4_switch_remove_ports(sw); if (tb_route(sw)) dev_info(&sw->dev, "device disconnected\n"); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 89e38aeea52b..948e7601428f 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -202,6 +202,7 @@ struct tb_switch { * @cap_tmu: Offset of the adapter specific TMU capability (%0 if not present) * @cap_adap: Offset of the adapter specific capability (%0 if not present) * @cap_usb4: Offset to the USB4 port capability (%0 if not present) + * @usb4: Pointer to the USB4 port structure (only if @cap_usb4 is != %0) * @port: Port number on switch * @disabled: Disabled by eeprom or enabled but not implemented * @bonded: true if the port is bonded (two lanes combined as one) @@ -228,6 +229,7 @@ struct tb_port { int cap_tmu; int cap_adap; int cap_usb4; + struct usb4_port *usb4; u8 port; bool disabled; bool bonded; @@ -241,6 +243,16 @@ struct tb_port { unsigned int dma_credits; }; +/** + * struct usb4_port - USB4 port device + * @dev: Device for the port + * @port: Pointer to the lane 0 adapter + */ +struct usb4_port { + struct device dev; + struct tb_port *port; +}; + /** * tb_retimer: Thunderbolt retimer * @dev: Device for the retimer @@ -645,6 +657,7 @@ struct tb *tb_probe(struct tb_nhi *nhi); extern struct device_type tb_domain_type; extern struct device_type tb_retimer_type; extern struct device_type tb_switch_type; +extern struct device_type usb4_port_device_type; int tb_domain_init(void); void tb_domain_exit(void); @@ -1038,6 +1051,8 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, const struct tb_port *port); struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port); +int usb4_switch_add_ports(struct tb_switch *sw); +void usb4_switch_remove_ports(struct tb_switch *sw); int usb4_port_unlock(struct tb_port *port); int usb4_port_configure(struct tb_port *port); @@ -1070,6 +1085,21 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw); +static inline bool tb_is_usb4_port_device(const struct device *dev) +{ + return dev->type == &usb4_port_device_type; +} + +static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev) +{ + if (tb_is_usb4_port_device(dev)) + return container_of(dev, struct usb4_port, dev); + return NULL; +} + +struct usb4_port *usb4_port_device_add(struct tb_port *port); +void usb4_port_device_remove(struct usb4_port *usb4); + /* Keep link controller awake during update */ #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 94cc25cc6388..1f82af35328e 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -985,6 +985,60 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, return NULL; } +/** + * usb4_switch_add_ports() - Add USB4 ports for this router + * @sw: USB4 router + * + * For USB4 router finds all USB4 ports and registers devices for each. + * Can be called to any router. + * + * Return %0 in case of success and negative errno in case of failure. + */ +int usb4_switch_add_ports(struct tb_switch *sw) +{ + struct tb_port *port; + + if (tb_switch_is_icm(sw) || !tb_switch_is_usb4(sw)) + return 0; + + tb_switch_for_each_port(sw, port) { + struct usb4_port *usb4; + + if (!tb_port_is_null(port)) + continue; + if (!port->cap_usb4) + continue; + + usb4 = usb4_port_device_add(port); + if (IS_ERR(usb4)) { + usb4_switch_remove_ports(sw); + return PTR_ERR(usb4); + } + + port->usb4 = usb4; + } + + return 0; +} + +/** + * usb4_switch_remove_ports() - Removes USB4 ports from this router + * @sw: USB4 router + * + * Unregisters previously registered USB4 ports. + */ +void usb4_switch_remove_ports(struct tb_switch *sw) +{ + struct tb_port *port; + + tb_switch_for_each_port(sw, port) { + if (port->usb4) { + usb4_port_device_remove(port->usb4); + port->usb4 = NULL; + } + } +} + /** * usb4_port_unlock() - Unlock USB4 downstream port * @port: USB4 port to unlock diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c new file mode 100644 index 000000000000..520bbfd7bf33 --- /dev/null +++ b/drivers/thunderbolt/usb4_port.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB4 port device + * + * Copyright (C) 2021, Intel Corporation + * Author: Mika Westerberg + */ + +#include +#include + +#include "tb.h" + +static ssize_t link_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + struct tb_port *port = usb4->port; + struct tb *tb = port->sw->tb; + const char *link; + + if (mutex_lock_interruptible(&tb->lock)) + return -ERESTARTSYS; + + if (tb_is_upstream_port(port)) + link = port->sw->link_usb4 ? "usb4" : "tbt"; + else if (tb_port_has_remote(port)) + link = port->remote->sw->link_usb4 ? "usb4" : "tbt"; + else + link = "none"; + + mutex_unlock(&tb->lock); + + return sysfs_emit(buf, "%s\n", link); +} +static DEVICE_ATTR_RO(link); + +static struct attribute *common_attrs[] = { + &dev_attr_link.attr, + NULL +}; + +static const struct attribute_group common_group = { + .attrs = common_attrs, +}; + +static const struct attribute_group *usb4_port_device_groups[] = { + &common_group, + NULL +}; + +static void usb4_port_device_release(struct device *dev) +{ + struct usb4_port *usb4 = container_of(dev, struct usb4_port, dev); + + kfree(usb4); +} + +struct device_type usb4_port_device_type = { + .name = "usb4_port", + .groups = usb4_port_device_groups, + .release = usb4_port_device_release, +}; + +/** + * usb4_port_device_add() - Add USB4 port device + * @port: Lane 0 adapter port to add the USB4 port + * + * Creates and registers a USB4 port device for @port. Returns the new + * USB4 port device pointer or ERR_PTR() in case of error. + */ +struct usb4_port *usb4_port_device_add(struct tb_port *port) +{ + struct usb4_port *usb4; + int ret; + + usb4 = kzalloc(sizeof(*usb4), GFP_KERNEL); + if (!usb4) + return ERR_PTR(-ENOMEM); + + usb4->port = port; + usb4->dev.type = &usb4_port_device_type; + usb4->dev.parent = &port->sw->dev; + dev_set_name(&usb4->dev, "usb4_port%d", port->port); + + ret = device_register(&usb4->dev); + if (ret) { + put_device(&usb4->dev); + return ERR_PTR(ret); + } + + pm_runtime_no_callbacks(&usb4->dev); + pm_runtime_set_active(&usb4->dev); + pm_runtime_enable(&usb4->dev); + pm_runtime_set_autosuspend_delay(&usb4->dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_mark_last_busy(&usb4->dev); + pm_runtime_use_autosuspend(&usb4->dev); + + return usb4; +} + +/** + * usb4_port_device_remove() - Removes USB4 port device + * @usb4: USB4 port device + * + * Unregisters the USB4 port device from the system. The device will be + * released when the last reference is dropped. + */ +void usb4_port_device_remove(struct usb4_port *usb4) +{ + device_unregister(&usb4->dev); +} -- cgit v1.2.3 From ccc5cb8ad5d18ec0e008d1652711fa1c18e9366c Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Thu, 1 Apr 2021 18:20:17 +0300 Subject: thunderbolt: Add support for ACPI _DSM to power on/off retimers Typically retimers can be accessed only when the USB4 link is up (e.g there is a cable connected). However, sometimes it is useful to be able to access retimers even if there is nothing connected to the USB4 port. For instance we may still want to be able to upgrade the retimer NVM firmware even if the user does not have any USB4 devices. This is something that USB4 spec leaves to implementers. In case of ACPI based systems, we can support this by providing a special _DSM method under each USB4 port. This _DSM can be used to turn on power to on-board retimers (and cycle it through different modes so that the sideband becomes usable). This patch adds support for this _DSM and makes the functionality available to the rest of the driver through tb_acpi_power_[on|off]_retimers(). Signed-off-by: Rajmohan Mani Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/acpi.c | 206 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/domain.c | 9 +- drivers/thunderbolt/tb.h | 13 +++ 3 files changed, 225 insertions(+), 3 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 35fa17f7e599..b67e72d5644b 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -180,3 +180,209 @@ bool tb_acpi_is_xdomain_allowed(void) return osc_sb_native_usb4_control & OSC_USB_XDOMAIN; return true; } + +/* UUID for retimer _DSM: e0053122-795b-4122-8a5e-57be1d26acb3 */ +static const guid_t retimer_dsm_guid = + GUID_INIT(0xe0053122, 0x795b, 0x4122, + 0x8a, 0x5e, 0x57, 0xbe, 0x1d, 0x26, 0xac, 0xb3); + +#define RETIMER_DSM_QUERY_ONLINE_STATE 1 +#define RETIMER_DSM_SET_ONLINE_STATE 2 + +static int tb_acpi_retimer_set_power(struct tb_port *port, bool power) +{ + struct usb4_port *usb4 = port->usb4; + union acpi_object argv4[2]; + struct acpi_device *adev; + union acpi_object *obj; + int ret; + + if (!usb4->can_offline) + return 0; + + adev = ACPI_COMPANION(&usb4->dev); + if (WARN_ON(!adev)) + return 0; + + /* Check if we are already powered on (and in correct mode) */ + obj = acpi_evaluate_dsm_typed(adev->handle, &retimer_dsm_guid, 1, + RETIMER_DSM_QUERY_ONLINE_STATE, NULL, + ACPI_TYPE_INTEGER); + if (!obj) { + tb_port_warn(port, "ACPI: query online _DSM failed\n"); + return -EIO; + } + + ret = obj->integer.value; + ACPI_FREE(obj); + + if (power == ret) + return 0; + + tb_port_dbg(port, "ACPI: calling _DSM to power %s retimers\n", + power ? "on" : "off"); + + argv4[0].type = ACPI_TYPE_PACKAGE; + argv4[0].package.count = 1; + argv4[0].package.elements = &argv4[1]; + argv4[1].integer.type = ACPI_TYPE_INTEGER; + argv4[1].integer.value = power; + + obj = acpi_evaluate_dsm_typed(adev->handle, &retimer_dsm_guid, 1, + RETIMER_DSM_SET_ONLINE_STATE, argv4, + ACPI_TYPE_INTEGER); + if (!obj) { + tb_port_warn(port, + "ACPI: set online state _DSM evaluation failed\n"); + return -EIO; + } + + ret = obj->integer.value; + ACPI_FREE(obj); + + if (ret >= 0) { + if (power) + return ret == 1 ? 0 : -EBUSY; + return 0; + } + + tb_port_warn(port, "ACPI: set online state _DSM failed with error %d\n", ret); + return -EIO; +} + +/** + * tb_acpi_power_on_retimers() - Call platform to power on retimers + * @port: USB4 port + * + * Calls platform to turn on power to all retimers behind this USB4 + * port. After this function returns successfully the caller can + * continue with the normal retimer flows (as specified in the USB4 + * spec). Note if this returns %-EBUSY it means the type-C port is in + * non-USB4/TBT mode (there is non-USB4/TBT device connected). + * + * This should only be called if the USB4/TBT link is not up. + * + * Returns %0 on success. + */ +int tb_acpi_power_on_retimers(struct tb_port *port) +{ + return tb_acpi_retimer_set_power(port, true); +} + +/** + * tb_acpi_power_off_retimers() - Call platform to power off retimers + * @port: USB4 port + * + * This is the opposite of tb_acpi_power_on_retimers(). After returning + * successfully the normal operations with the @port can continue. + * + * Returns %0 on success. + */ +int tb_acpi_power_off_retimers(struct tb_port *port) +{ + return tb_acpi_retimer_set_power(port, false); +} + +static bool tb_acpi_bus_match(struct device *dev) +{ + return tb_is_switch(dev) || tb_is_usb4_port_device(dev); +} + +static struct acpi_device *tb_acpi_find_port(struct acpi_device *adev, + const struct tb_port *port) +{ + struct acpi_device *port_adev; + + if (!adev) + return NULL; + + /* + * Device routers exists under the downstream facing USB4 port + * of the parent router. Their _ADR is always 0. + */ + list_for_each_entry(port_adev, &adev->children, node) { + if (acpi_device_adr(port_adev) == port->port) + return port_adev; + } + + return NULL; +} + +static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw) +{ + struct acpi_device *adev = NULL; + struct tb_switch *parent_sw; + + parent_sw = tb_switch_parent(sw); + if (parent_sw) { + struct tb_port *port = tb_port_at(tb_route(sw), parent_sw); + struct acpi_device *port_adev; + + port_adev = tb_acpi_find_port(ACPI_COMPANION(&parent_sw->dev), port); + if (port_adev) + adev = acpi_find_child_device(port_adev, 0, false); + } else { + struct tb_nhi *nhi = sw->tb->nhi; + struct acpi_device *parent_adev; + + parent_adev = ACPI_COMPANION(&nhi->pdev->dev); + if (parent_adev) + adev = acpi_find_child_device(parent_adev, 0, false); + } + + return adev; +} + +static struct acpi_device *tb_acpi_find_companion(struct device *dev) +{ + /* + * The Thunderbolt/USB4 hierarchy looks like following: + * + * Device (NHI) + * Device (HR) // Host router _ADR == 0 + * Device (DFP0) // Downstream port _ADR == lane 0 adapter + * Device (DR) // Device router _ADR == 0 + * Device (UFP) // Upstream port _ADR == lane 0 adapter + * Device (DFP1) // Downstream port _ADR == lane 0 adapter number + * + * At the moment we bind the host router to the corresponding + * Linux device. + */ + if (tb_is_switch(dev)) + return tb_acpi_switch_find_companion(tb_to_switch(dev)); + else if (tb_is_usb4_port_device(dev)) + return tb_acpi_find_port(ACPI_COMPANION(dev->parent), + tb_to_usb4_port_device(dev)->port); + return NULL; +} + +static void tb_acpi_setup(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + + if (!adev || !usb4) + return; + + if (acpi_check_dsm(adev->handle, &retimer_dsm_guid, 1, + BIT(RETIMER_DSM_QUERY_ONLINE_STATE) | + BIT(RETIMER_DSM_SET_ONLINE_STATE))) + usb4->can_offline = true; +} + +static struct acpi_bus_type tb_acpi_bus = { + .name = "thunderbolt", + .match = tb_acpi_bus_match, + .find_companion = tb_acpi_find_companion, + .setup = tb_acpi_setup, +}; + +int tb_acpi_init(void) +{ + return register_acpi_bus_type(&tb_acpi_bus); +} + +void tb_acpi_exit(void) +{ + unregister_acpi_bus_type(&tb_acpi_bus); +} diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 98f4056f89ff..a062befcb3b2 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -881,11 +881,12 @@ int tb_domain_init(void) int ret; tb_test_init(); - tb_debugfs_init(); + tb_acpi_init(); + ret = tb_xdomain_init(); if (ret) - goto err_debugfs; + goto err_acpi; ret = bus_register(&tb_bus_type); if (ret) goto err_xdomain; @@ -894,7 +895,8 @@ int tb_domain_init(void) err_xdomain: tb_xdomain_exit(); -err_debugfs: +err_acpi: + tb_acpi_exit(); tb_debugfs_exit(); tb_test_exit(); @@ -907,6 +909,7 @@ void tb_domain_exit(void) ida_destroy(&tb_domain_ida); tb_nvm_exit(); tb_xdomain_exit(); + tb_acpi_exit(); tb_debugfs_exit(); tb_test_exit(); } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 948e7601428f..c5704f495afa 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -247,10 +247,13 @@ struct tb_port { * struct usb4_port - USB4 port device * @dev: Device for the port * @port: Pointer to the lane 0 adapter + * @can_offline: Does the port have necessary platform support to moved + * it into offline mode and back */ struct usb4_port { struct device dev; struct tb_port *port; + bool can_offline; }; /** @@ -1113,6 +1116,11 @@ bool tb_acpi_may_tunnel_usb3(void); bool tb_acpi_may_tunnel_dp(void); bool tb_acpi_may_tunnel_pcie(void); bool tb_acpi_is_xdomain_allowed(void); + +int tb_acpi_init(void); +void tb_acpi_exit(void); +int tb_acpi_power_on_retimers(struct tb_port *port); +int tb_acpi_power_off_retimers(struct tb_port *port); #else static inline void tb_acpi_add_links(struct tb_nhi *nhi) { } @@ -1121,6 +1129,11 @@ static inline bool tb_acpi_may_tunnel_usb3(void) { return true; } static inline bool tb_acpi_may_tunnel_dp(void) { return true; } static inline bool tb_acpi_may_tunnel_pcie(void) { return true; } static inline bool tb_acpi_is_xdomain_allowed(void) { return true; } + +static inline int tb_acpi_init(void) { return 0; } +static inline void tb_acpi_exit(void) { } +static inline int tb_acpi_power_on_retimers(struct tb_port *port) { return 0; } +static inline int tb_acpi_power_off_retimers(struct tb_port *port) { return 0; } #endif #ifdef CONFIG_DEBUG_FS -- cgit v1.2.3 From 3406de7cc20f254010f2f17450a58541fb77ffea Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Thu, 1 Apr 2021 18:38:05 +0300 Subject: thunderbolt: Add additional USB4 port operations for retimer access When accessing retimers when there is no cable connected we are going to need additional USB4 port operations. First the port needs to be put into offline mode, and then the sideband channel transactions must be enabled on the SBTX line. This adds support for these operations. Signed-off-by: Rajmohan Mani Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/sb_regs.h | 2 ++ drivers/thunderbolt/tb.h | 3 ++ drivers/thunderbolt/usb4.c | 69 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h index 9dafd696612f..bda889ff3bda 100644 --- a/drivers/thunderbolt/sb_regs.h +++ b/drivers/thunderbolt/sb_regs.h @@ -17,7 +17,9 @@ enum usb4_sb_opcode { USB4_SB_OPCODE_ERR = 0x20525245, /* "ERR " */ USB4_SB_OPCODE_ONS = 0x444d4321, /* "!CMD" */ + USB4_SB_OPCODE_ROUTER_OFFLINE = 0x4e45534c, /* "LSEN" */ USB4_SB_OPCODE_ENUMERATE_RETIMERS = 0x4d554e45, /* "ENUM" */ + USB4_SB_OPCODE_SET_INBOUND_SBTX = 0x5055534c, /* "LSUP" */ USB4_SB_OPCODE_QUERY_LAST_RETIMER = 0x5453414c, /* "LAST" */ USB4_SB_OPCODE_GET_NVM_SECTOR_SIZE = 0x53534e47, /* "GNSS" */ USB4_SB_OPCODE_NVM_SET_OFFSET = 0x53504f42, /* "BOPS" */ diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index c5704f495afa..936518adca74 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1062,8 +1062,11 @@ 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_router_offline(struct tb_port *port); +int usb4_port_router_online(struct tb_port *port); int usb4_port_enumerate_retimers(struct tb_port *port); +int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index); int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf, u8 size); int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg, diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 1f82af35328e..8af96dbaa7a7 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -1318,6 +1318,48 @@ static int usb4_port_sb_op(struct tb_port *port, enum usb4_sb_target target, return -ETIMEDOUT; } +static int usb4_port_set_router_offline(struct tb_port *port, bool offline) +{ + u32 val = !offline; + int ret; + + ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0, + USB4_SB_METADATA, &val, sizeof(val)); + if (ret) + return ret; + + val = USB4_SB_OPCODE_ROUTER_OFFLINE; + return usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0, + USB4_SB_OPCODE, &val, sizeof(val)); +} + +/** + * usb4_port_router_offline() - Put the USB4 port to offline mode + * @port: USB4 port + * + * This function puts the USB4 port into offline mode. In this mode the + * port does not react on hotplug events anymore. This needs to be + * called before retimer access is done when the USB4 links is not up. + * + * Returns %0 in case of success and negative errno if there was an + * error. + */ +int usb4_port_router_offline(struct tb_port *port) +{ + return usb4_port_set_router_offline(port, true); +} + +/** + * usb4_port_router_online() - Put the USB4 port back to online + * @port: USB4 port + * + * Makes the USB4 port functional again. + */ +int usb4_port_router_online(struct tb_port *port) +{ + return usb4_port_set_router_offline(port, false); +} + /** * usb4_port_enumerate_retimers() - Send RT broadcast transaction * @port: USB4 port @@ -1343,6 +1385,33 @@ static inline int usb4_port_retimer_op(struct tb_port *port, u8 index, timeout_msec); } +/** + * usb4_port_retimer_set_inbound_sbtx() - Enable sideband channel transactions + * @port: USB4 port + * @index: Retimer index + * + * Enables sideband channel transations on SBTX. Can be used when USB4 + * link does not go up, for example if there is no device connected. + */ +int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index) +{ + int ret; + + ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_SET_INBOUND_SBTX, + 500); + + if (ret != -ENODEV) + return ret; + + /* + * Per the USB4 retimer spec, the retimer is not required to + * send an RT (Retimer Transaction) response for the first + * SET_INBOUND_SBTX command + */ + return usb4_port_retimer_op(port, index, USB4_SB_OPCODE_SET_INBOUND_SBTX, + 500); +} + /** * usb4_port_retimer_read() - Read from retimer sideband registers * @port: USB4 port -- cgit v1.2.3 From 3fb10ea4ce86d4d06622be894099c59872e92c57 Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Thu, 1 Apr 2021 18:42:38 +0300 Subject: thunderbolt: Add support for retimer NVM upgrade when there is no link With help from platform firmware (ACPI) it is possible to power on retimers even when there is no USB4 link (e.g nothing is connected to the USB4 ports). This allows us to bring the USB4 sideband up so that we can access retimers and upgrade their NVM firmware. If the platform has support for this, we expose two additional attributes under USB4 ports: offline and rescan. These can be used to bring the port offline, rescan for the retimers and put the port online again. The retimer NVM upgrade itself works the same way than with cable connected. Signed-off-by: Rajmohan Mani Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/retimer.c | 24 +++--- drivers/thunderbolt/switch.c | 48 ++++++++---- drivers/thunderbolt/tb.c | 4 +- drivers/thunderbolt/tb.h | 5 +- drivers/thunderbolt/usb4_port.c | 169 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 28 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 2e5188fb1150..05af0feefe84 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -401,19 +401,18 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index) /** * tb_retimer_scan() - Scan for on-board retimers under port * @port: USB4 port to scan + * @add: If true also registers found retimers * - * Tries to enumerate on-board retimers connected to @port. Found - * retimers are registered as children of @port. Does not scan for cable - * retimers for now. + * Brings the sideband into a state where retimers can be accessed. + * Then Tries to enumerate on-board retimers connected to @port. Found + * retimers are registered as children of @port if @add is set. Does + * not scan for cable retimers for now. */ -int tb_retimer_scan(struct tb_port *port) +int tb_retimer_scan(struct tb_port *port, bool add) { u32 status[TB_MAX_RETIMER_INDEX + 1] = {}; int ret, i, last_idx = 0; - if (!port->cap_usb4) - return 0; - /* * Send broadcast RT to make sure retimer indices facing this * port are set. @@ -422,6 +421,13 @@ int tb_retimer_scan(struct tb_port *port) if (ret) return ret; + /* + * Enable sideband channel for each retimer. We can do this + * regardless whether there is device connected or not. + */ + for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) + usb4_port_retimer_set_inbound_sbtx(port, i); + /* * Before doing anything else, read the authentication status. * If the retimer has it set, store it for the new retimer @@ -453,10 +459,10 @@ int tb_retimer_scan(struct tb_port *port) rt = tb_port_find_retimer(port, i); if (rt) { put_device(&rt->dev); - } else { + } else if (add) { ret = tb_retimer_add(port, i, status[i]); if (ret && ret != -EOPNOTSUPP) - return ret; + break; } } diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 7303c61a891a..dae59919e2bf 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1153,6 +1153,33 @@ static int tb_port_start_lane_initialization(struct tb_port *port) return ret == -EINVAL ? 0 : ret; } +/* + * Returns true if the port had something (router, XDomain) connected + * before suspend. + */ +static bool tb_port_resume(struct tb_port *port) +{ + bool has_remote = tb_port_has_remote(port); + + if (port->usb4) { + usb4_port_device_resume(port->usb4); + } else if (!has_remote) { + /* + * For disconnected downstream lane adapters start lane + * initialization now so we detect future connects. + * + * For XDomain start the lane initialzation now so the + * link gets re-established. + * + * This is only needed for non-USB4 ports. + */ + if (!tb_is_upstream_port(port) || port->xdomain) + tb_port_start_lane_initialization(port); + } + + return has_remote || port->xdomain; +} + /** * tb_port_is_enabled() - Is the adapter port enabled * @port: Port to check @@ -2915,22 +2942,11 @@ int tb_switch_resume(struct tb_switch *sw) /* check for surviving downstream switches */ tb_switch_for_each_port(sw, port) { - if (!tb_port_has_remote(port) && !port->xdomain) { - /* - * For disconnected downstream lane adapters - * start lane initialization now so we detect - * future connects. - */ - if (!tb_is_upstream_port(port) && tb_port_is_null(port)) - tb_port_start_lane_initialization(port); + if (!tb_port_is_null(port)) + continue; + + if (!tb_port_resume(port)) continue; - } else if (port->xdomain) { - /* - * Start lane initialization for XDomain so the - * link gets re-established. - */ - tb_port_start_lane_initialization(port); - } if (tb_wait_for_port(port, true) <= 0) { tb_port_warn(port, @@ -2939,7 +2955,7 @@ int tb_switch_resume(struct tb_switch *sw) tb_sw_set_unplugged(port->remote->sw); else if (port->xdomain) port->xdomain->is_unplugged = true; - } else if (tb_port_has_remote(port) || port->xdomain) { + } else { /* * Always unlock the port so the downstream * switch/domain is accessible. diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 7e6dc2b03bed..bc6d568dbb89 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -595,7 +595,7 @@ static void tb_scan_port(struct tb_port *port) return; } - tb_retimer_scan(port); + tb_retimer_scan(port, true); sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, tb_downstream_route(port)); @@ -662,7 +662,7 @@ static void tb_scan_port(struct tb_port *port) tb_sw_warn(sw, "failed to enable TMU\n"); /* Scan upstream retimers */ - tb_retimer_scan(upstream_port); + tb_retimer_scan(upstream_port, true); /* * Create USB 3.x tunnels only when the switch is plugged to the diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 936518adca74..341e8443a22d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -249,11 +249,13 @@ struct tb_port { * @port: Pointer to the lane 0 adapter * @can_offline: Does the port have necessary platform support to moved * it into offline mode and back + * @offline: The port is currently in offline mode */ struct usb4_port { struct device dev; struct tb_port *port; bool can_offline; + bool offline; }; /** @@ -1017,7 +1019,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd); struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, u8 depth); -int tb_retimer_scan(struct tb_port *port); +int tb_retimer_scan(struct tb_port *port, bool add); void tb_retimer_remove_all(struct tb_port *port); static inline bool tb_is_retimer(const struct device *dev) @@ -1105,6 +1107,7 @@ static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev) struct usb4_port *usb4_port_device_add(struct tb_port *port); void usb4_port_device_remove(struct usb4_port *usb4); +int usb4_port_device_resume(struct usb4_port *usb4); /* Keep link controller awake during update */ #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 520bbfd7bf33..765c74179598 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -44,8 +44,166 @@ static const struct attribute_group common_group = { .attrs = common_attrs, }; +static int usb4_port_offline(struct usb4_port *usb4) +{ + struct tb_port *port = usb4->port; + int ret; + + ret = tb_acpi_power_on_retimers(port); + if (ret) + return ret; + + ret = usb4_port_router_offline(port); + if (ret) { + tb_acpi_power_off_retimers(port); + return ret; + } + + ret = tb_retimer_scan(port, false); + if (ret) { + usb4_port_router_online(port); + tb_acpi_power_off_retimers(port); + } + + return ret; +} + +static void usb4_port_online(struct usb4_port *usb4) +{ + struct tb_port *port = usb4->port; + + usb4_port_router_online(port); + tb_acpi_power_off_retimers(port); +} + +static ssize_t offline_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + + return sysfs_emit(buf, "%d\n", usb4->offline); +} + +static ssize_t offline_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + struct tb_port *port = usb4->port; + struct tb *tb = port->sw->tb; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + pm_runtime_get_sync(&usb4->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm; + } + + if (val == usb4->offline) + goto out_unlock; + + /* Offline mode works only for ports that are not connected */ + if (tb_port_has_remote(port)) { + ret = -EBUSY; + goto out_unlock; + } + + if (val) { + ret = usb4_port_offline(usb4); + if (ret) + goto out_unlock; + } else { + usb4_port_online(usb4); + tb_retimer_remove_all(port); + } + + usb4->offline = val; + tb_port_dbg(port, "%s offline mode\n", val ? "enter" : "exit"); + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm: + pm_runtime_mark_last_busy(&usb4->dev); + pm_runtime_put_autosuspend(&usb4->dev); + + return ret ? ret : count; +} +static DEVICE_ATTR_RW(offline); + +static ssize_t rescan_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + struct tb_port *port = usb4->port; + struct tb *tb = port->sw->tb; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + if (!val) + return count; + + pm_runtime_get_sync(&usb4->dev); + + if (mutex_lock_interruptible(&tb->lock)) { + ret = -ERESTARTSYS; + goto out_rpm; + } + + /* Must be in offline mode already */ + if (!usb4->offline) { + ret = -EINVAL; + goto out_unlock; + } + + tb_retimer_remove_all(port); + ret = tb_retimer_scan(port, true); + +out_unlock: + mutex_unlock(&tb->lock); +out_rpm: + pm_runtime_mark_last_busy(&usb4->dev); + pm_runtime_put_autosuspend(&usb4->dev); + + return ret ? ret : count; +} +static DEVICE_ATTR_WO(rescan); + +static struct attribute *service_attrs[] = { + &dev_attr_offline.attr, + &dev_attr_rescan.attr, + NULL +}; + +static umode_t service_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb4_port *usb4 = tb_to_usb4_port_device(dev); + + /* + * Always need some platform help to cycle the modes so that + * retimers can be accessed through the sideband. + */ + return usb4->can_offline ? attr->mode : 0; +} + +static const struct attribute_group service_group = { + .attrs = service_attrs, + .is_visible = service_attr_is_visible, +}; + static const struct attribute_group *usb4_port_device_groups[] = { &common_group, + &service_group, NULL }; @@ -110,3 +268,14 @@ void usb4_port_device_remove(struct usb4_port *usb4) { device_unregister(&usb4->dev); } + +/** + * usb4_port_device_resume() - Resumes USB4 port device + * @usb4: USB4 port device + * + * Used to resume USB4 port device after sleep state. + */ +int usb4_port_device_resume(struct usb4_port *usb4) +{ + return usb4->offline ? usb4_port_offline(usb4) : 0; +} -- cgit v1.2.3 From ff3a8306456755689babc7bcc29c60e582738c7b Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Mon, 12 Apr 2021 14:01:46 +0300 Subject: thunderbolt: Move nvm_write_ops to tb.h Currently these write ops are used for updating router firmware images only. Moving to tb.h helps the retimers also to use the same ops. Also add tb_ prefix to the enum while there. Signed-off-by: Rajmohan Mani Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 5 ----- drivers/thunderbolt/tb.h | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index dae59919e2bf..bf4821d3bbab 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -26,11 +26,6 @@ struct nvm_auth_status { u32 status; }; -enum nvm_write_ops { - WRITE_AND_AUTHENTICATE = 1, - WRITE_ONLY = 2, -}; - /* * Hold NVM authentication failure status per switch This information * needs to stay around even when the switch gets power cycled so we diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 341e8443a22d..863d80ad44ab 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -58,6 +58,11 @@ struct tb_nvm { bool flushed; }; +enum tb_nvm_write_ops { + WRITE_AND_AUTHENTICATE = 1, + WRITE_ONLY = 2, +}; + #define TB_SWITCH_KEY_SIZE 32 #define TB_SWITCH_MAX_DEPTH 6 #define USB4_SWITCH_MAX_DEPTH 5 -- cgit v1.2.3 From 1cbf680f7687f55ae5a1405556519bc70d66a616 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 12 Apr 2021 15:25:08 +0300 Subject: thunderbolt: Allow router NVM authenticate separately It may be useful if the actual NVM authentication can be delayed to be run later, for instance when the user logs out. For this reason add a new NVM operation (AUHENTICATE_ONLY) that just triggers the authentication procedure over whatever was written to the NVM storage. This is not supported with Thunderbolt 1-3 devices, though. Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 50 ++++++++++++++++++++++++++++---------------- drivers/thunderbolt/tb.h | 2 ++ drivers/thunderbolt/usb4.c | 13 ++++++++++-- 3 files changed, 45 insertions(+), 20 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index bf4821d3bbab..83b1ef3d5d03 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -303,13 +303,23 @@ static inline int nvm_read(struct tb_switch *sw, unsigned int address, return dma_port_flash_read(sw->dma_port, address, buf, size); } -static int nvm_authenticate(struct tb_switch *sw) +static int nvm_authenticate(struct tb_switch *sw, bool auth_only) { int ret; - if (tb_switch_is_usb4(sw)) + if (tb_switch_is_usb4(sw)) { + if (auth_only) { + ret = usb4_switch_nvm_set_offset(sw, 0); + if (ret) + return ret; + } + sw->nvm->authenticating = true; return usb4_switch_nvm_authenticate(sw); + } else if (auth_only) { + return -EOPNOTSUPP; + } + sw->nvm->authenticating = true; if (!tb_route(sw)) { nvm_authenticate_start_dma_port(sw); ret = nvm_authenticate_host_dma_port(sw); @@ -1713,8 +1723,7 @@ static ssize_t nvm_authenticate_sysfs(struct device *dev, const char *buf, bool disconnect) { struct tb_switch *sw = tb_to_switch(dev); - int val; - int ret; + int val, ret; pm_runtime_get_sync(&sw->dev); @@ -1737,22 +1746,27 @@ static ssize_t nvm_authenticate_sysfs(struct device *dev, const char *buf, nvm_clear_auth_status(sw); if (val > 0) { - if (!sw->nvm->flushed) { - if (!sw->nvm->buf) { + if (val == AUTHENTICATE_ONLY) { + if (disconnect) ret = -EINVAL; - goto exit_unlock; + else + ret = nvm_authenticate(sw, true); + } else { + if (!sw->nvm->flushed) { + if (!sw->nvm->buf) { + ret = -EINVAL; + goto exit_unlock; + } + + ret = nvm_validate_and_write(sw); + if (ret || val == WRITE_ONLY) + goto exit_unlock; } - - ret = nvm_validate_and_write(sw); - if (ret || val == WRITE_ONLY) - goto exit_unlock; - } - if (val == WRITE_AND_AUTHENTICATE) { - if (disconnect) { - ret = tb_lc_force_power(sw); - } else { - sw->nvm->authenticating = true; - ret = nvm_authenticate(sw); + if (val == WRITE_AND_AUTHENTICATE) { + if (disconnect) + ret = tb_lc_force_power(sw); + else + ret = nvm_authenticate(sw, false); } } } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 863d80ad44ab..53f6bb85b178 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -61,6 +61,7 @@ struct tb_nvm { enum tb_nvm_write_ops { WRITE_AND_AUTHENTICATE = 1, WRITE_ONLY = 2, + AUTHENTICATE_ONLY = 3, }; #define TB_SWITCH_KEY_SIZE 32 @@ -1049,6 +1050,7 @@ int usb4_switch_set_sleep(struct tb_switch *sw); int usb4_switch_nvm_sector_size(struct tb_switch *sw); int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size); +int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address); int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, const void *buf, size_t size); int usb4_switch_nvm_authenticate(struct tb_switch *sw); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 8af96dbaa7a7..76d7335aa440 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -547,8 +547,17 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, usb4_switch_nvm_read_block, sw); } -static int usb4_switch_nvm_set_offset(struct tb_switch *sw, - unsigned int address) +/** + * usb4_switch_nvm_set_offset() - Set NVM write offset + * @sw: USB4 router + * @address: Start offset + * + * Explicitly sets NVM write offset. Normally when writing to NVM this + * is done automatically by usb4_switch_nvm_write(). + * + * Returns %0 in success and negative errno if there was a failure. + */ +int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address) { u32 metadata, dwaddress; u8 status = 0; -- cgit v1.2.3 From faa1c615f0bdd4f3ac5288bf2952f49dfeac916c Mon Sep 17 00:00:00 2001 From: Rajmohan Mani Date: Mon, 12 Apr 2021 15:29:16 +0300 Subject: thunderbolt: Add WRITE_ONLY and AUTHENTICATE_ONLY NVM operations for retimers The same way we support these two operations for USB4 routers we can extend the retimer NVM operations to support retimers also. Signed-off-by: Rajmohan Mani Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/retimer.c | 51 +++++++++++++++++++++++++++++++------------ drivers/thunderbolt/tb.h | 2 ++ drivers/thunderbolt/usb4.c | 15 +++++++++++-- 3 files changed, 52 insertions(+), 16 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 05af0feefe84..3aa790aa6500 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -103,6 +103,7 @@ static int tb_retimer_nvm_validate_and_write(struct tb_retimer *rt) unsigned int image_size, hdr_size; const u8 *buf = rt->nvm->buf; u16 ds_size, device; + int ret; image_size = rt->nvm->buf_data_size; if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE) @@ -140,8 +141,25 @@ static int tb_retimer_nvm_validate_and_write(struct tb_retimer *rt) buf += hdr_size; image_size -= hdr_size; - return usb4_port_retimer_nvm_write(rt->port, rt->index, 0, buf, - image_size); + ret = usb4_port_retimer_nvm_write(rt->port, rt->index, 0, buf, + image_size); + if (!ret) + rt->nvm->flushed = true; + + return ret; +} + +static int tb_retimer_nvm_authenticate(struct tb_retimer *rt, bool auth_only) +{ + int ret; + + if (auth_only) { + ret = usb4_port_retimer_nvm_set_offset(rt->port, rt->index, 0); + if (ret) + return ret; + } + + return usb4_port_retimer_nvm_authenticate(rt->port, rt->index); } static ssize_t device_show(struct device *dev, struct device_attribute *attr, @@ -176,8 +194,7 @@ static ssize_t nvm_authenticate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct tb_retimer *rt = tb_to_retimer(dev); - bool val; - int ret; + int val, ret; pm_runtime_get_sync(&rt->dev); @@ -191,7 +208,7 @@ static ssize_t nvm_authenticate_store(struct device *dev, goto exit_unlock; } - ret = kstrtobool(buf, &val); + ret = kstrtoint(buf, 10, &val); if (ret) goto exit_unlock; @@ -199,16 +216,22 @@ static ssize_t nvm_authenticate_store(struct device *dev, rt->auth_status = 0; if (val) { - if (!rt->nvm->buf) { - ret = -EINVAL; - goto exit_unlock; + if (val == AUTHENTICATE_ONLY) { + ret = tb_retimer_nvm_authenticate(rt, true); + } else { + if (!rt->nvm->flushed) { + if (!rt->nvm->buf) { + ret = -EINVAL; + goto exit_unlock; + } + + ret = tb_retimer_nvm_validate_and_write(rt); + if (ret || val == WRITE_ONLY) + goto exit_unlock; + } + if (val == WRITE_AND_AUTHENTICATE) + ret = tb_retimer_nvm_authenticate(rt, false); } - - ret = tb_retimer_nvm_validate_and_write(rt); - if (ret) - goto exit_unlock; - - ret = usb4_port_retimer_nvm_authenticate(rt->port, rt->index); } exit_unlock: diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 53f6bb85b178..725104c83e3d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1082,6 +1082,8 @@ int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg, const void *buf, u8 size); int usb4_port_retimer_is_last(struct tb_port *port, u8 index); int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index); +int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, + unsigned int address); int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address, const void *buf, size_t size); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 76d7335aa440..ceddbe7e9f93 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -1513,8 +1513,19 @@ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) return ret ? ret : metadata & USB4_NVM_SECTOR_SIZE_MASK; } -static int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, - unsigned int address) +/** + * usb4_port_retimer_nvm_set_offset() - Set NVM write offset + * @port: USB4 port + * @index: Retimer index + * @address: Start offset + * + * Exlicitly sets NVM write offset. Normally when writing to NVM this is + * done automatically by usb4_port_retimer_nvm_write(). + * + * Returns %0 in success and negative errno if there was a failure. + */ +int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, + unsigned int address) { u32 metadata, dwaddress; int ret; -- cgit v1.2.3 From 25335b30daf66f4cc03715c2ac9cdc3258fb5531 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 21 Apr 2021 17:14:10 +0300 Subject: thunderbolt: Check for NVM authentication status after the operation started If the NVM authentication fails immediately, like if the firmware detects that the image is not valid for some reason, better to read the status once and if set to non-zero fail the operation accordingly. Signed-off-by: Mika Westerberg Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/retimer.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 3aa790aa6500..722694052f4a 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -151,6 +151,7 @@ static int tb_retimer_nvm_validate_and_write(struct tb_retimer *rt) static int tb_retimer_nvm_authenticate(struct tb_retimer *rt, bool auth_only) { + u32 status; int ret; if (auth_only) { @@ -159,7 +160,24 @@ static int tb_retimer_nvm_authenticate(struct tb_retimer *rt, bool auth_only) return ret; } - return usb4_port_retimer_nvm_authenticate(rt->port, rt->index); + ret = usb4_port_retimer_nvm_authenticate(rt->port, rt->index); + if (ret) + return ret; + + usleep_range(100, 150); + + /* + * Check the status now if we still can access the retimer. It + * is expected that the below fails. + */ + ret = usb4_port_retimer_nvm_authenticate_status(rt->port, rt->index, + &status); + if (!ret) { + rt->auth_status = status; + return status ? -EINVAL : 0; + } + + return 0; } static ssize_t device_show(struct device *dev, struct device_attribute *attr, -- cgit v1.2.3 From a0d36fa1065901f939b04587a09c65303a64ac88 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 7 Jun 2021 13:37:46 +0300 Subject: thunderbolt: Bond lanes only when dual_link_port != NULL in alloc_dev_default() We should not dereference ->dual_link_port if it is NULL and lane bonding is requested. For this reason move lane bonding configuration happen inside the block where ->dual_link_port != NULL. Fixes: 54509f5005ca ("thunderbolt: Add KUnit tests for path walking") Reported-by: kernel test robot Reported-by: Dan Carpenter Reviewed-by: Yehezkel Bernat Signed-off-by: Mika Westerberg --- drivers/thunderbolt/test.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index cf34c1ecf5d5..ba5afd471766 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -303,18 +303,18 @@ static struct tb_switch *alloc_dev_default(struct kunit *test, if (port->dual_link_port && upstream_port->dual_link_port) { port->dual_link_port->remote = upstream_port->dual_link_port; upstream_port->dual_link_port->remote = port->dual_link_port; - } - if (bonded) { - /* Bonding is used */ - port->bonded = true; - port->total_credits *= 2; - port->dual_link_port->bonded = true; - port->dual_link_port->total_credits = 0; - upstream_port->bonded = true; - upstream_port->total_credits *= 2; - upstream_port->dual_link_port->bonded = true; - upstream_port->dual_link_port->total_credits = 0; + if (bonded) { + /* Bonding is used */ + port->bonded = true; + port->total_credits *= 2; + port->dual_link_port->bonded = true; + port->dual_link_port->total_credits = 0; + upstream_port->bonded = true; + upstream_port->total_credits *= 2; + upstream_port->dual_link_port->bonded = true; + upstream_port->dual_link_port->total_credits = 0; + } } return sw; -- cgit v1.2.3 From 349bfe089d02f18b05ed2c386eeb248a0be49641 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 24 May 2021 17:57:32 +0300 Subject: thunderbolt: Add device links only when software connection manager is used We only need to set up the device links when software connection manager path is used. The firmware connection manager does not need them and if they are present they may even cause problems. Reviewed-by: Yehezkel Bernat Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 67 ----------------------------------------------- drivers/thunderbolt/tb.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 67 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index a0386d1e3fc9..478bf6701145 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -17,7 +17,6 @@ #include #include #include -#include #include "nhi.h" #include "nhi_regs.h" @@ -1127,69 +1126,6 @@ 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 struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; @@ -1278,9 +1214,6 @@ 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 = nhi_select_cm(nhi); if (!tb) { dev_err(&nhi->pdev->dev, diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index bc6d568dbb89..2897a77d44c3 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "tb.h" #include "tb_regs.h" @@ -1571,6 +1572,69 @@ static const struct tb_cm_ops tb_cm_ops = { .disconnect_xdomain_paths = tb_disconnect_xdomain_paths, }; +/* + * 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)); + } + } +} + struct tb *tb_probe(struct tb_nhi *nhi) { struct tb_cm *tcm; @@ -1594,5 +1658,8 @@ struct tb *tb_probe(struct tb_nhi *nhi) tb_dbg(tb, "using software connection manager\n"); + tb_apple_add_links(nhi); + tb_acpi_add_links(nhi); + return tb; } -- cgit v1.2.3 From 0172e411450a479d65061014edf48933d4209c93 Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Tue, 1 Jun 2021 16:32:29 +0300 Subject: thunderbolt: Poll 10ms for REG_FW_STS_NVM_AUTH_DONE to be set In Intel Tiger Lake and beyond it takes some time after the force power is set until the firmware connection manager is ready. So instead of reading it once we poll it for 10ms before giving up. Signed-off-by: Gil Fine Reviewed-by: Yehezkel Bernat Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 2f30b816705a..0f25cf9fe519 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -1677,14 +1677,18 @@ 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; + unsigned long end = jiffies + msecs_to_jiffies(10); - /* - * If the firmware is not running use software CM. This platform - * should fully support both. - */ - val = ioread32(tb->nhi->iobase + REG_FW_STS); - return !!(val & REG_FW_STS_NVM_AUTH_DONE); + do { + u32 val; + + val = ioread32(tb->nhi->iobase + REG_FW_STS); + if (val & REG_FW_STS_NVM_AUTH_DONE) + return true; + usleep_range(100, 500); + } while (time_before(jiffies, end)); + + return false; } static void icm_handle_notification(struct work_struct *work) -- cgit v1.2.3 From 2a8b519ece3bd9a9fd4d890df64adafa68d18036 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 7 Jun 2021 13:33:30 +0300 Subject: thunderbolt: No need to include in usb4_port.c This include is not needed so drop it. Reported-by: kernel test robot Reviewed-by: Yehezkel Bernat Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4_port.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 765c74179598..29e2a4f9c9f5 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -6,7 +6,6 @@ * Author: Mika Westerberg */ -#include #include #include "tb.h" -- cgit v1.2.3 From 135794868ad83d0327cdd78df469e118f1fe7cc4 Mon Sep 17 00:00:00 2001 From: Azhar Shaikh Date: Thu, 22 Apr 2021 14:46:16 -0700 Subject: thunderbolt: Add support for Intel Alder Lake Alder Lake has the same integrated Thunderbolt/USB4 controller as Intel Tiger Lake. By default it is still using firmware based connection manager so we can use most of the Tiger Lake flows. Add the Alder Lake PCI IDs to the driver list of supported devices. Signed-off-by: Azhar Shaikh Reviewed-by: Yehezkel Bernat Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 2 ++ drivers/thunderbolt/nhi.c | 4 ++++ drivers/thunderbolt/nhi.h | 2 ++ 3 files changed, 8 insertions(+) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 0f25cf9fe519..6255f1ef9599 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -2509,6 +2509,8 @@ struct tb *icm_probe(struct tb_nhi *nhi) case PCI_DEVICE_ID_INTEL_TGL_NHI1: case PCI_DEVICE_ID_INTEL_TGL_H_NHI0: case PCI_DEVICE_ID_INTEL_TGL_H_NHI1: + case PCI_DEVICE_ID_INTEL_ADL_NHI0: + case PCI_DEVICE_ID_INTEL_ADL_NHI1: icm->is_supported = icm_tgl_is_supported; icm->driver_ready = icm_icl_driver_ready; icm->set_uuid = icm_icl_set_uuid; diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 478bf6701145..fa44332845a1 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1333,6 +1333,10 @@ static struct pci_device_id nhi_ids[] = { .driver_data = (kernel_ulong_t)&icl_nhi_ops }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, /* Any USB4 compliant host */ { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 69770beca792..69083aab2736 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -72,6 +72,8 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE 0x15ea #define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI 0x15eb #define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE 0x15ef +#define PCI_DEVICE_ID_INTEL_ADL_NHI0 0x463e +#define PCI_DEVICE_ID_INTEL_ADL_NHI1 0x466d #define PCI_DEVICE_ID_INTEL_ICL_NHI1 0x8a0d #define PCI_DEVICE_ID_INTEL_ICL_NHI0 0x8a17 #define PCI_DEVICE_ID_INTEL_TGL_NHI0 0x9a1b -- cgit v1.2.3 From b18f901382fdb74a138a0bf30458c54a023a1d86 Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Mon, 14 Jun 2021 16:52:10 +0300 Subject: thunderbolt: Fix DROM handling for USB4 DROM DROM for USB4 host/device has a shorter header than Thunderbolt DROM header. This patch addresses host/device with USB4 DROM (According to spec: Universal Serial Bus 4 (USB4) Device ROM Specification, Rev 1.0, Feb-2021). While there correct the data_len field to be 12 bits and rename __unknown1 to reserved following the spec. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/eeprom.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers/thunderbolt') diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 46d0906a3070..470885e6f1c8 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -214,7 +214,10 @@ static u32 tb_crc32(void *data, size_t len) return ~__crc32c_le(~0, data, len); } -#define TB_DROM_DATA_START 13 +#define TB_DROM_DATA_START 13 +#define TB_DROM_HEADER_SIZE 22 +#define USB4_DROM_HEADER_SIZE 16 + struct tb_drom_header { /* BYTE 0 */ u8 uid_crc8; /* checksum for uid */ @@ -224,9 +227,9 @@ struct tb_drom_header { u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */ /* BYTE 13 */ u8 device_rom_revision; /* should be <= 1 */ - u16 data_len:10; - u8 __unknown1:6; - /* BYTES 16-21 */ + u16 data_len:12; + u8 reserved:4; + /* BYTES 16-21 - Only for TBT DROM, nonexistent in USB4 DROM */ u16 vendor_id; u16 model_id; u8 model_rev; @@ -401,10 +404,10 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw, * * Drom must have been copied to sw->drom. */ -static int tb_drom_parse_entries(struct tb_switch *sw) +static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size) { struct tb_drom_header *header = (void *) sw->drom; - u16 pos = sizeof(*header); + u16 pos = header_size; u16 drom_size = header->data_len + TB_DROM_DATA_START; int res; @@ -566,7 +569,7 @@ static int tb_drom_parse(struct tb_switch *sw) header->data_crc32, crc); } - return tb_drom_parse_entries(sw); + return tb_drom_parse_entries(sw, TB_DROM_HEADER_SIZE); } static int usb4_drom_parse(struct tb_switch *sw) @@ -583,7 +586,7 @@ static int usb4_drom_parse(struct tb_switch *sw) return -EINVAL; } - return tb_drom_parse_entries(sw); + return tb_drom_parse_entries(sw, USB4_DROM_HEADER_SIZE); } /** -- cgit v1.2.3