From patchwork Tue Jun 16 13:56:12 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 11607501 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C699D618 for ; Tue, 16 Jun 2020 13:56:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AEB4620882 for ; Tue, 16 Jun 2020 13:56:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729097AbgFPN4X (ORCPT ); Tue, 16 Jun 2020 09:56:23 -0400 Received: from mga06.intel.com ([134.134.136.31]:57244 "EHLO mga06.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726306AbgFPN4W (ORCPT ); Tue, 16 Jun 2020 09:56:22 -0400 IronPort-SDR: oamvqpVwpyGvSOStPFH9PPXp61WTiDmrbUH6QzGqMhvtKd5EBonfJgNxdhNDi0LouHtDdz533o zE0FFGmGfDmA== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Jun 2020 06:56:21 -0700 IronPort-SDR: sFqJpY9s+DUo6ssg/PTDljlaXoJpeZDEiPGlvfb47WNEU3QZ6+sd8i0JnFXpR4mzMzkQktPMtS KzNGrmaFcmcg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,518,1583222400"; d="scan'208";a="298915034" Received: from black.fi.intel.com ([10.237.72.28]) by fmsmga004.fm.intel.com with ESMTP; 16 Jun 2020 06:56:19 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id 1D898109; Tue, 16 Jun 2020 16:56:18 +0300 (EEST) From: Mika Westerberg To: linux-usb@vger.kernel.org Cc: Andreas Noever , Michael Jamet , Mika Westerberg , Yehezkel Bernat , Kranthi Kuntala , Rajmohan Mani , Mario.Limonciello@dell.com, Greg Kroah-Hartman , Lukas Wunner Subject: [PATCH 1/6] thunderbolt: Add Intel USB-IF ID to the NVM upgrade supported list Date: Tue, 16 Jun 2020 16:56:12 +0300 Message-Id: <20200616135617.85752-2-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.27.0.rc2 In-Reply-To: <20200616135617.85752-1-mika.westerberg@linux.intel.com> References: <20200616135617.85752-1-mika.westerberg@linux.intel.com> MIME-Version: 1.0 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org With USB4 Intel is also using its USB-IF ID (0x8087) with the new devices. The NVM format is the same. Add this to the driver so NVM upgrade is possible with these devices as well. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c01176429d5f..6659b2b0663d 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -423,7 +423,8 @@ static int tb_switch_nvm_add(struct tb_switch *sw) * currently restrict NVM upgrade for Intel hardware. We may * relax this in the future when we learn other NVM formats. */ - if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) { + if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL && + sw->config.vendor_id != 0x8087) { dev_info(&sw->dev, "NVM format of vendor %#x is not known, disabling NVM upgrade\n", sw->config.vendor_id); From patchwork Tue Jun 16 13:56:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 11607505 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 682B7912 for ; Tue, 16 Jun 2020 13:56:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4BAFC20882 for ; Tue, 16 Jun 2020 13:56:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729104AbgFPN4Z (ORCPT ); Tue, 16 Jun 2020 09:56:25 -0400 Received: from mga18.intel.com ([134.134.136.126]:55684 "EHLO mga18.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728908AbgFPN4W (ORCPT ); Tue, 16 Jun 2020 09:56:22 -0400 IronPort-SDR: vB65qlksK3h9z66FTvX4pgqhEm6R71kGyrPZq39iVAAR9MGLSSKPp0Gtz4KzoCZjAMF/DkJ0FF IpvyDbL+SAvg== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga001.jf.intel.com ([10.7.209.18]) by orsmga106.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Jun 2020 06:56:21 -0700 IronPort-SDR: Jv/6cJPnkfd6Qq83HY2nAfkBqfo1xz+DAMDu3+SXcenyOVZy3luUab5+wQWQCDJClhzEkIdsTG +kBS0Ni2dyDg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,518,1583222400"; d="scan'208";a="351735220" Received: from black.fi.intel.com ([10.237.72.28]) by orsmga001.jf.intel.com with ESMTP; 16 Jun 2020 06:56:19 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id 283542AA; Tue, 16 Jun 2020 16:56:18 +0300 (EEST) From: Mika Westerberg To: linux-usb@vger.kernel.org Cc: Andreas Noever , Michael Jamet , Mika Westerberg , Yehezkel Bernat , Kranthi Kuntala , Rajmohan Mani , Mario.Limonciello@dell.com, Greg Kroah-Hartman , Lukas Wunner Subject: [PATCH 2/6] thunderbolt: Split common NVM functionality into a separate file Date: Tue, 16 Jun 2020 16:56:13 +0300 Message-Id: <20200616135617.85752-3-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.27.0.rc2 In-Reply-To: <20200616135617.85752-1-mika.westerberg@linux.intel.com> References: <20200616135617.85752-1-mika.westerberg@linux.intel.com> MIME-Version: 1.0 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org We are going to reuse some of this functionality to implement retimer NVM upgrade so move common NVM functionality into its own file. We also rename the structure from tb_switch_nvm to tb_nvm to make it clear that it is not just for switches. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/Makefile | 1 + drivers/thunderbolt/domain.c | 2 +- drivers/thunderbolt/nvm.c | 169 +++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 116 +++++------------------- drivers/thunderbolt/tb.h | 31 +++++-- 5 files changed, 220 insertions(+), 99 deletions(-) create mode 100644 drivers/thunderbolt/nvm.c diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 68f7a19690d8..7ee257cee7ff 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -2,5 +2,6 @@ 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 obj-${CONFIG_USB4_KUNIT_TEST} += test.o diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 68c1b93ac5d9..bba4cbfa9759 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -812,6 +812,6 @@ void tb_domain_exit(void) { bus_unregister(&tb_bus_type); ida_destroy(&tb_domain_ida); - tb_switch_exit(); + tb_nvm_exit(); tb_xdomain_exit(); } diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c new file mode 100644 index 000000000000..4c6aa06ab3d5 --- /dev/null +++ b/drivers/thunderbolt/nvm.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NVM helpers + * + * Copyright (C) 2020, Intel Corporation + * Author: Mika Westerberg + */ + +#include +#include +#include + +#include "tb.h" + +static DEFINE_IDA(nvm_ida); + +/** + * tb_nvm_alloc() - Allocate new NVM structure + * @dev: Device owning the NVM + * + * Allocates new NVM structure with unique @id and returns it. In case + * of error returns ERR_PTR(). + */ +struct tb_nvm *tb_nvm_alloc(struct device *dev) +{ + struct tb_nvm *nvm; + int ret; + + nvm = kzalloc(sizeof(*nvm), GFP_KERNEL); + if (!nvm) + return ERR_PTR(-ENOMEM); + + ret = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL); + if (ret < 0) { + kfree(nvm); + return ERR_PTR(ret); + } + + nvm->id = ret; + nvm->dev = dev; + + return nvm; +} + +/** + * tb_nvm_add_active() - Adds active NVMem device to NVM + * @nvm: NVM structure + * @size: Size of the active NVM in bytes + * @reg_read: Pointer to the function to read the NVM (passed directly to the + * NVMem device) + * + * Registers new active NVmem device for @nvm. The @reg_read is called + * directly from NVMem so it must handle possible concurrent access if + * needed. The first parameter passed to @reg_read is @nvm structure. + * Returns %0 in success and negative errno otherwise. + */ +int tb_nvm_add_active(struct tb_nvm *nvm, size_t size, nvmem_reg_read_t reg_read) +{ + struct nvmem_config config; + struct nvmem_device *nvmem; + + memset(&config, 0, sizeof(config)); + + config.name = "nvm_active"; + config.reg_read = reg_read; + config.read_only = true; + config.id = nvm->id; + config.stride = 4; + config.word_size = 4; + config.size = size; + config.dev = nvm->dev; + config.owner = THIS_MODULE; + config.priv = nvm; + + nvmem = nvmem_register(&config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + nvm->active = nvmem; + return 0; +} + +/** + * tb_nvm_write_buf() - Write data to @nvm buffer + * @nvm: NVM structure + * @offset: Offset where to write the data + * @val: Data buffer to write + * @bytes: Number of bytes to write + * + * Helper function to cache the new NVM image before it is actually + * written to the flash. Copies @bytes from @val to @nvm->buf starting + * from @offset. + */ +int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, + size_t bytes) +{ + if (!nvm->buf) { + nvm->buf = vmalloc(NVM_MAX_SIZE); + if (!nvm->buf) + return -ENOMEM; + } + + nvm->buf_data_size = offset + bytes; + memcpy(nvm->buf + offset, val, bytes); + return 0; +} + +/** + * tb_nvm_add_non_active() - Adds non-active NVMem device to NVM + * @nvm: NVM structure + * @size: Size of the non-active NVM in bytes + * @reg_write: Pointer to the function to write the NVM (passed directly + * to the NVMem device) + * + * Registers new non-active NVmem device for @nvm. The @reg_write is called + * directly from NVMem so it must handle possible concurrent access if + * needed. The first parameter passed to @reg_write is @nvm structure. + * Returns %0 in success and negative errno otherwise. + */ +int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size, + nvmem_reg_write_t reg_write) +{ + struct nvmem_config config; + struct nvmem_device *nvmem; + + memset(&config, 0, sizeof(config)); + + config.name = "nvm_non_active"; + config.reg_write = reg_write; + config.root_only = true; + config.id = nvm->id; + config.stride = 4; + config.word_size = 4; + config.size = size; + config.dev = nvm->dev; + config.owner = THIS_MODULE; + config.priv = nvm; + + nvmem = nvmem_register(&config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + nvm->non_active = nvmem; + return 0; +} + +/** + * tb_nvm_free() - Release NVM and its resources + * @nvm: NVM structure to release + * + * Releases NVM and the NVMem devices if they were registered. + */ +void tb_nvm_free(struct tb_nvm *nvm) +{ + if (nvm) { + if (nvm->non_active) + nvmem_unregister(nvm->non_active); + if (nvm->active) + nvmem_unregister(nvm->active); + vfree(nvm->buf); + ida_simple_remove(&nvm_ida, nvm->id); + } + kfree(nvm); +} + +void tb_nvm_exit(void) +{ + ida_destroy(&nvm_ida); +} diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 6659b2b0663d..c8ed614f14e6 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -13,21 +13,12 @@ #include #include #include -#include #include "tb.h" /* Switch NVM support */ -#define NVM_DEVID 0x05 -#define NVM_VERSION 0x08 #define NVM_CSS 0x10 -#define NVM_FLASH_SIZE 0x45 - -#define NVM_MIN_SIZE SZ_32K -#define NVM_MAX_SIZE SZ_512K - -static DEFINE_IDA(nvm_ida); struct nvm_auth_status { struct list_head list; @@ -328,7 +319,8 @@ static int nvm_authenticate(struct tb_switch *sw) static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val, size_t bytes) { - struct tb_switch *sw = priv; + struct tb_nvm *nvm = priv; + struct tb_switch *sw = tb_to_switch(nvm->dev); int ret; pm_runtime_get_sync(&sw->dev); @@ -351,8 +343,9 @@ static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val, static int tb_switch_nvm_write(void *priv, unsigned int offset, void *val, size_t bytes) { - struct tb_switch *sw = priv; - int ret = 0; + struct tb_nvm *nvm = priv; + struct tb_switch *sw = tb_to_switch(nvm->dev); + int ret; if (!mutex_trylock(&sw->tb->lock)) return restart_syscall(); @@ -363,55 +356,15 @@ static int tb_switch_nvm_write(void *priv, unsigned int offset, void *val, * locally here and handle the special cases when the user asks * us to authenticate the image. */ - if (!sw->nvm->buf) { - sw->nvm->buf = vmalloc(NVM_MAX_SIZE); - if (!sw->nvm->buf) { - ret = -ENOMEM; - goto unlock; - } - } - - sw->nvm->buf_data_size = offset + bytes; - memcpy(sw->nvm->buf + offset, val, bytes); - -unlock: + ret = tb_nvm_write_buf(nvm, offset, val, bytes); mutex_unlock(&sw->tb->lock); return ret; } -static struct nvmem_device *register_nvmem(struct tb_switch *sw, int id, - size_t size, bool active) -{ - struct nvmem_config config; - - memset(&config, 0, sizeof(config)); - - if (active) { - config.name = "nvm_active"; - config.reg_read = tb_switch_nvm_read; - config.read_only = true; - } else { - config.name = "nvm_non_active"; - config.reg_write = tb_switch_nvm_write; - config.root_only = true; - } - - config.id = id; - config.stride = 4; - config.word_size = 4; - config.size = size; - config.dev = &sw->dev; - config.owner = THIS_MODULE; - config.priv = sw; - - return nvmem_register(&config); -} - static int tb_switch_nvm_add(struct tb_switch *sw) { - struct nvmem_device *nvm_dev; - struct tb_switch_nvm *nvm; + struct tb_nvm *nvm; u32 val; int ret; @@ -431,11 +384,9 @@ static int tb_switch_nvm_add(struct tb_switch *sw) return 0; } - nvm = kzalloc(sizeof(*nvm), GFP_KERNEL); - if (!nvm) - return -ENOMEM; - - nvm->id = ida_simple_get(&nvm_ida, 0, 0, GFP_KERNEL); + nvm = tb_nvm_alloc(&sw->dev); + if (IS_ERR(nvm)) + return PTR_ERR(nvm); /* * If the switch is in safe-mode the only accessible portion of @@ -447,7 +398,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw) ret = nvm_read(sw, NVM_FLASH_SIZE, &val, sizeof(val)); if (ret) - goto err_ida; + goto err_nvm; hdr_size = sw->generation < 3 ? SZ_8K : SZ_16K; nvm_size = (SZ_1M << (val & 7)) / 8; @@ -455,44 +406,34 @@ static int tb_switch_nvm_add(struct tb_switch *sw) ret = nvm_read(sw, NVM_VERSION, &val, sizeof(val)); if (ret) - goto err_ida; + goto err_nvm; nvm->major = val >> 16; nvm->minor = val >> 8; - nvm_dev = register_nvmem(sw, nvm->id, nvm_size, true); - if (IS_ERR(nvm_dev)) { - ret = PTR_ERR(nvm_dev); - goto err_ida; - } - nvm->active = nvm_dev; + ret = tb_nvm_add_active(nvm, nvm_size, tb_switch_nvm_read); + if (ret) + goto err_nvm; } if (!sw->no_nvm_upgrade) { - nvm_dev = register_nvmem(sw, nvm->id, NVM_MAX_SIZE, false); - if (IS_ERR(nvm_dev)) { - ret = PTR_ERR(nvm_dev); - goto err_nvm_active; - } - nvm->non_active = nvm_dev; + ret = tb_nvm_add_non_active(nvm, NVM_MAX_SIZE, + tb_switch_nvm_write); + if (ret) + goto err_nvm; } sw->nvm = nvm; return 0; -err_nvm_active: - if (nvm->active) - nvmem_unregister(nvm->active); -err_ida: - ida_simple_remove(&nvm_ida, nvm->id); - kfree(nvm); - +err_nvm: + tb_nvm_free(nvm); return ret; } static void tb_switch_nvm_remove(struct tb_switch *sw) { - struct tb_switch_nvm *nvm; + struct tb_nvm *nvm; nvm = sw->nvm; sw->nvm = NULL; @@ -504,13 +445,7 @@ static void tb_switch_nvm_remove(struct tb_switch *sw) if (!nvm->authenticating) nvm_clear_auth_status(sw); - if (nvm->non_active) - nvmem_unregister(nvm->non_active); - if (nvm->active) - nvmem_unregister(nvm->active); - ida_simple_remove(&nvm_ida, nvm->id); - vfree(nvm->buf); - kfree(nvm); + tb_nvm_free(nvm); } /* port utility functions */ @@ -2772,8 +2707,3 @@ struct tb_port *tb_switch_find_port(struct tb_switch *sw, return NULL; } - -void tb_switch_exit(void) -{ - ida_destroy(&nvm_ida); -} diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a62db231f07b..3d54f36f8805 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -18,8 +18,17 @@ #include "ctl.h" #include "dma_port.h" +#define NVM_MIN_SIZE SZ_32K +#define NVM_MAX_SIZE SZ_512K + +/* Intel specific NVM offsets */ +#define NVM_DEVID 0x05 +#define NVM_VERSION 0x08 +#define NVM_FLASH_SIZE 0x45 + /** - * struct tb_switch_nvm - Structure holding switch NVM information + * struct tb_nvm - Structure holding NVM information + * @dev: Owner of the NVM * @major: Major version number of the active NVM portion * @minor: Minor version number of the active NVM portion * @id: Identifier used with both NVM portions @@ -29,9 +38,13 @@ * the actual NVM flash device * @buf_data_size: Number of bytes actually consumed by the new NVM * image - * @authenticating: The switch is authenticating the new NVM + * @authenticating: The device is authenticating the new NVM + * + * The user of this structure needs to handle serialization of possible + * concurrent access. */ -struct tb_switch_nvm { +struct tb_nvm { + struct device *dev; u8 major; u8 minor; int id; @@ -143,7 +156,7 @@ struct tb_switch { int cap_lc; bool is_unplugged; u8 *drom; - struct tb_switch_nvm *nvm; + struct tb_nvm *nvm; bool no_nvm_upgrade; bool safe_mode; bool boot; @@ -544,7 +557,6 @@ extern struct device_type tb_switch_type; int tb_domain_init(void); void tb_domain_exit(void); -void tb_switch_exit(void); int tb_xdomain_init(void); void tb_xdomain_exit(void); @@ -577,6 +589,15 @@ static inline void tb_domain_put(struct tb *tb) put_device(&tb->dev); } +struct tb_nvm *tb_nvm_alloc(struct device *dev); +int tb_nvm_add_active(struct tb_nvm *nvm, size_t size, nvmem_reg_read_t reg_read); +int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, + size_t bytes); +int tb_nvm_add_non_active(struct tb_nvm *nvm, size_t size, + nvmem_reg_write_t reg_write); +void tb_nvm_free(struct tb_nvm *nvm); +void tb_nvm_exit(void); + 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, From patchwork Tue Jun 16 13:56:14 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 11607497 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B14E214E3 for ; Tue, 16 Jun 2020 13:56:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A3E8D207C3 for ; Tue, 16 Jun 2020 13:56:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729075AbgFPN4W (ORCPT ); Tue, 16 Jun 2020 09:56:22 -0400 Received: from mga01.intel.com ([192.55.52.88]:35320 "EHLO mga01.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726306AbgFPN4V (ORCPT ); Tue, 16 Jun 2020 09:56:21 -0400 IronPort-SDR: vZELSGqaylEUuHdWcjbMdAJKhSXLp33zDwtZnAy1FG/SdOE5iAe2LT7MKrel4OGBATovPkFSMU UZgrO0k+wAfQ== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by fmsmga101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Jun 2020 06:56:21 -0700 IronPort-SDR: Omny9yRFdHWcsuFY2NQl5vPUVrcGyyz4Zat0iVXrbY9Z7XKN1rDBFG4/ErAeEsO8RIhJQqw7VR tVnV13H4GBmw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,518,1583222400"; d="scan'208";a="382876302" Received: from black.fi.intel.com ([10.237.72.28]) by fmsmga001.fm.intel.com with ESMTP; 16 Jun 2020 06:56:18 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id 379938CA; Tue, 16 Jun 2020 16:56:18 +0300 (EEST) From: Mika Westerberg To: linux-usb@vger.kernel.org Cc: Andreas Noever , Michael Jamet , Mika Westerberg , Yehezkel Bernat , Kranthi Kuntala , Rajmohan Mani , Mario.Limonciello@dell.com, Greg Kroah-Hartman , Lukas Wunner Subject: [PATCH 3/6] thunderbolt: Generalize usb4_switch_do_[read|write]_data() Date: Tue, 16 Jun 2020 16:56:14 +0300 Message-Id: <20200616135617.85752-4-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.27.0.rc2 In-Reply-To: <20200616135617.85752-1-mika.westerberg@linux.intel.com> References: <20200616135617.85752-1-mika.westerberg@linux.intel.com> MIME-Version: 1.0 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Currently these functions operate on struct tb_switch but we are going to need the same functionality with retimers as well so make the two functions work with an arbitrary object that gets passed as parameter to the callbacks. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 39 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index d1a554fd09ae..8a83857573df 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -42,8 +42,8 @@ enum usb4_switch_op { #define USB4_NVM_SECTOR_SIZE_MASK GENMASK(23, 0) -typedef int (*read_block_fn)(struct tb_switch *, unsigned int, void *, size_t); -typedef int (*write_block_fn)(struct tb_switch *, const void *, size_t); +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) @@ -95,8 +95,8 @@ static int usb4_switch_op_write_metadata(struct tb_switch *sw, u32 metadata) return tb_sw_write(sw, &metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); } -static int usb4_switch_do_read_data(struct tb_switch *sw, u16 address, - void *buf, size_t size, read_block_fn read_block) +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; @@ -113,7 +113,7 @@ static int usb4_switch_do_read_data(struct tb_switch *sw, u16 address, dwaddress = address / 4; dwords = ALIGN(nbytes, 4) / 4; - ret = read_block(sw, dwaddress, data, dwords); + ret = read_block(read_block_data, dwaddress, data, dwords); if (ret) { if (ret == -ETIMEDOUT) { if (retries--) @@ -133,8 +133,8 @@ static int usb4_switch_do_read_data(struct tb_switch *sw, u16 address, return 0; } -static int usb4_switch_do_write_data(struct tb_switch *sw, u16 address, - const void *buf, size_t size, write_block_fn write_next_block) +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; @@ -149,7 +149,7 @@ static int usb4_switch_do_write_data(struct tb_switch *sw, u16 address, memcpy(data + offset, buf, nbytes); - ret = write_next_block(sw, data, nbytes / 4); + ret = write_next_block(write_block_data, data, nbytes / 4); if (ret) { if (ret == -ETIMEDOUT) { if (retries--) @@ -289,10 +289,11 @@ int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid) return tb_sw_read(sw, uid, TB_CFG_SWITCH, ROUTER_CS_7, 2); } -static int usb4_switch_drom_read_block(struct tb_switch *sw, +static int usb4_switch_drom_read_block(void *data, unsigned int dwaddress, void *buf, size_t dwords) { + struct tb_switch *sw = data; u8 status = 0; u32 metadata; int ret; @@ -329,8 +330,8 @@ static int usb4_switch_drom_read_block(struct tb_switch *sw, int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) { - return usb4_switch_do_read_data(sw, address, buf, size, - usb4_switch_drom_read_block); + return usb4_do_read_data(address, buf, size, + usb4_switch_drom_read_block, sw); } static int usb4_set_port_configured(struct tb_port *port, bool configured) @@ -463,9 +464,10 @@ int usb4_switch_nvm_sector_size(struct tb_switch *sw) return metadata & USB4_NVM_SECTOR_SIZE_MASK; } -static int usb4_switch_nvm_read_block(struct tb_switch *sw, +static int usb4_switch_nvm_read_block(void *data, unsigned int dwaddress, void *buf, size_t dwords) { + struct tb_switch *sw = data; u8 status = 0; u32 metadata; int ret; @@ -502,8 +504,8 @@ static int usb4_switch_nvm_read_block(struct tb_switch *sw, int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) { - return usb4_switch_do_read_data(sw, address, buf, size, - usb4_switch_nvm_read_block); + return usb4_do_read_data(address, buf, size, + usb4_switch_nvm_read_block, sw); } static int usb4_switch_nvm_set_offset(struct tb_switch *sw, @@ -528,9 +530,10 @@ static int usb4_switch_nvm_set_offset(struct tb_switch *sw, return status ? -EIO : 0; } -static int usb4_switch_nvm_write_next_block(struct tb_switch *sw, - const void *buf, size_t dwords) +static int usb4_switch_nvm_write_next_block(void *data, const void *buf, + size_t dwords) { + struct tb_switch *sw = data; u8 status; int ret; @@ -564,8 +567,8 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, if (ret) return ret; - return usb4_switch_do_write_data(sw, address, buf, size, - usb4_switch_nvm_write_next_block); + return usb4_do_write_data(address, buf, size, + usb4_switch_nvm_write_next_block, sw); } /** From patchwork Tue Jun 16 13:56:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 11607499 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CEEB4618 for ; Tue, 16 Jun 2020 13:56:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B709D20882 for ; Tue, 16 Jun 2020 13:56:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729081AbgFPN4W (ORCPT ); Tue, 16 Jun 2020 09:56:22 -0400 Received: from mga03.intel.com ([134.134.136.65]:26083 "EHLO mga03.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729067AbgFPN4W (ORCPT ); Tue, 16 Jun 2020 09:56:22 -0400 IronPort-SDR: U3/rBKXfMQ2tUGt+mJ7hc+AocJz2c9DihSjswzXD+m0rpwUeZj0afLVHNljcpN+/+3KqJPuCG7 u5ZZMUlt0J5g== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga103.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Jun 2020 06:56:21 -0700 IronPort-SDR: w/XUM3NmMQ/R1JG02Nrj1guZmLUb8bvfr4/NKACmCqQ4dykgUUXmCpqCXypNET3sYOS+wsuxZv 47+GDb0hbjKQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,518,1583222400"; d="scan'208";a="309115288" Received: from black.fi.intel.com ([10.237.72.28]) by fmsmga002.fm.intel.com with ESMTP; 16 Jun 2020 06:56:19 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id 3D364795; Tue, 16 Jun 2020 16:56:18 +0300 (EEST) From: Mika Westerberg To: linux-usb@vger.kernel.org Cc: Andreas Noever , Michael Jamet , Mika Westerberg , Yehezkel Bernat , Kranthi Kuntala , Rajmohan Mani , Mario.Limonciello@dell.com, Greg Kroah-Hartman , Lukas Wunner Subject: [PATCH 4/6] thunderbolt: Retry USB4 block read operation Date: Tue, 16 Jun 2020 16:56:15 +0300 Message-Id: <20200616135617.85752-5-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.27.0.rc2 In-Reply-To: <20200616135617.85752-1-mika.westerberg@linux.intel.com> References: <20200616135617.85752-1-mika.westerberg@linux.intel.com> MIME-Version: 1.0 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Especially when accessing retimers over USB4 sideband operations the possibility to get read errors seems to be higher so make the usb4_do_read_data() retry a couple of times if it sees any other error than -ENODEV (device is gone). We can only do this for read side because it carries the offset as part of metadata (as opposed to writes). Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 8a83857573df..142c7244bdb1 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -115,11 +115,8 @@ static int usb4_do_read_data(u16 address, void *buf, size_t size, ret = read_block(read_block_data, dwaddress, data, dwords); if (ret) { - if (ret == -ETIMEDOUT) { - if (retries--) - continue; - ret = -EIO; - } + if (ret != -ENODEV && retries--) + continue; return ret; } From patchwork Tue Jun 16 13:56:16 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 11607529 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 73999618 for ; Tue, 16 Jun 2020 13:58:53 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 575E7207D3 for ; Tue, 16 Jun 2020 13:58:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728869AbgFPN6w (ORCPT ); Tue, 16 Jun 2020 09:58:52 -0400 Received: from mga14.intel.com ([192.55.52.115]:36502 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726606AbgFPN6w (ORCPT ); Tue, 16 Jun 2020 09:58:52 -0400 IronPort-SDR: aQRfo4m+9Rb8aFITVybexRgAKU03vvlexfw6lkSCaflM1SOTrYIh6AR8mmJyiA0QDrlNTm20Vi Ji1eK2sRfJvw== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga103.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Jun 2020 06:56:25 -0700 IronPort-SDR: ufyo5RXoY36EtWNGcdU+yl64i9cynnGzP5kxE8y6GiPSUk+qyJ6bcYWIJQuWipw7KMD6XTw8Ab xlm6lucLxZkA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,518,1583222400"; d="scan'208";a="420769364" Received: from black.fi.intel.com ([10.237.72.28]) by orsmga004.jf.intel.com with ESMTP; 16 Jun 2020 06:56:22 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id 4DC58913; Tue, 16 Jun 2020 16:56:18 +0300 (EEST) From: Mika Westerberg To: linux-usb@vger.kernel.org Cc: Andreas Noever , Michael Jamet , Mika Westerberg , Yehezkel Bernat , Kranthi Kuntala , Rajmohan Mani , Mario.Limonciello@dell.com, Greg Kroah-Hartman , Lukas Wunner Subject: [PATCH 5/6] thunderbolt: Implement USB4 port sideband operations for retimer access Date: Tue, 16 Jun 2020 16:56:16 +0300 Message-Id: <20200616135617.85752-6-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.27.0.rc2 In-Reply-To: <20200616135617.85752-1-mika.westerberg@linux.intel.com> References: <20200616135617.85752-1-mika.westerberg@linux.intel.com> MIME-Version: 1.0 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: Rajmohan Mani USB4 spec specifies standard set of sideband operations that are send over the low speed link to access either retimers on the link or the link parter (the other router). The USB4 retimer spec extends these and adds operations for retimer NVM upgrade. This implements the retimer access and NVM upgrade USB4 port sideband operations which we need for retimer support in the patch that follows. Signed-off-by: Rajmohan Mani Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg --- drivers/thunderbolt/sb_regs.h | 31 +++ drivers/thunderbolt/tb.h | 16 ++ drivers/thunderbolt/tb_regs.h | 10 + drivers/thunderbolt/usb4.c | 459 ++++++++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 drivers/thunderbolt/sb_regs.h diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h new file mode 100644 index 000000000000..0e587b7b9200 --- /dev/null +++ b/drivers/thunderbolt/sb_regs.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USB4 port sideband registers found on routers and retimers + * + * Copyright (C) 2020, Intel Corporation + * Authors: Mika Westerberg + * Rajmohan Mani + */ + +#ifndef _SB_REGS +#define _SB_REGS + +#define USB4_SB_OPCODE 0x08 + +enum usb4_sb_opcode { + USB4_SB_OPCODE_ERR = 0x20525245, /* "ERR " */ + USB4_SB_OPCODE_ONS = 0x444d4321, /* "!CMD" */ + USB4_SB_OPCODE_ENUMERATE_RETIMERS = 0x4d554e45, /* "ENUM" */ + 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" */ + USB4_SB_OPCODE_NVM_BLOCK_WRITE = 0x574b4c42, /* "BLKW" */ + USB4_SB_OPCODE_NVM_AUTH_WRITE = 0x48545541, /* "AUTH" */ + USB4_SB_OPCODE_NVM_READ = 0x52524641, /* "AFRR" */ +}; + +#define USB4_SB_METADATA 0x09 +#define USB4_SB_METADATA_NVM_AUTH_WRITE_MASK GENMASK(5, 0) +#define USB4_SB_DATA 0x12 + +#endif diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 3d54f36f8805..8f840148378a 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -876,6 +876,22 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port); int usb4_port_unlock(struct tb_port *port); +int usb4_port_enumerate_retimers(struct tb_port *port); + +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, + 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_write(struct tb_port *port, u8 index, + unsigned int address, const void *buf, + size_t size); +int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index); +int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index, + u32 *status); +int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index, + unsigned int address, void *buf, size_t size); int usb4_usb3_port_max_link_rate(struct tb_port *port); int usb4_usb3_port_actual_link_rate(struct tb_port *port); diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 4fc561347b7c..2ac6af8e0c13 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -288,6 +288,16 @@ struct tb_regs_port_header { #define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT 20 /* USB4 port registers */ +#define PORT_CS_1 0x01 +#define PORT_CS_1_LENGTH_SHIFT 8 +#define PORT_CS_1_TARGET_MASK GENMASK(18, 16) +#define PORT_CS_1_TARGET_SHIFT 16 +#define PORT_CS_1_RETIMER_INDEX_SHIFT 20 +#define PORT_CS_1_WNR_WRITE BIT(24) +#define PORT_CS_1_NR BIT(25) +#define PORT_CS_1_RC BIT(26) +#define PORT_CS_1_PND BIT(31) +#define PORT_CS_2 0x02 #define PORT_CS_18 0x12 #define PORT_CS_18_BE BIT(8) #define PORT_CS_18_TCM BIT(9) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 142c7244bdb1..966f334b4010 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -10,6 +10,7 @@ #include #include +#include "sb_regs.h" #include "tb.h" #define USB4_DATA_DWORDS 16 @@ -27,6 +28,12 @@ enum usb4_switch_op { USB4_SWITCH_OP_NVM_SECTOR_SIZE = 0x25, }; +enum usb4_sb_target { + USB4_SB_TARGET_ROUTER, + USB4_SB_TARGET_PARTNER, + USB4_SB_TARGET_RETIMER, +}; + #define USB4_NVM_READ_OFFSET_MASK GENMASK(23, 2) #define USB4_NVM_READ_OFFSET_SHIFT 2 #define USB4_NVM_READ_LENGTH_MASK GENMASK(27, 24) @@ -810,6 +817,458 @@ static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit, return -ETIMEDOUT; } +static int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords) +{ + if (dwords > USB4_DATA_DWORDS) + return -EINVAL; + + return tb_port_read(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, + dwords); +} + +static int usb4_port_write_data(struct tb_port *port, const void *data, + size_t dwords) +{ + if (dwords > USB4_DATA_DWORDS) + return -EINVAL; + + return tb_port_write(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2, + dwords); +} + +static int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, + u8 index, u8 reg, void *buf, u8 size) +{ + size_t dwords = DIV_ROUND_UP(size, 4); + int ret; + u32 val; + + if (!port->cap_usb4) + return -EINVAL; + + val = reg; + val |= size << PORT_CS_1_LENGTH_SHIFT; + val |= (target << PORT_CS_1_TARGET_SHIFT) & PORT_CS_1_TARGET_MASK; + if (target == USB4_SB_TARGET_RETIMER) + val |= (index << PORT_CS_1_RETIMER_INDEX_SHIFT); + val |= PORT_CS_1_PND; + + ret = tb_port_write(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_1, 1); + if (ret) + return ret; + + ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_1, + PORT_CS_1_PND, 0, 500); + if (ret) + return ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_1, 1); + if (ret) + return ret; + + if (val & PORT_CS_1_NR) + return -ENODEV; + if (val & PORT_CS_1_RC) + return -EIO; + + return buf ? usb4_port_read_data(port, buf, dwords) : 0; +} + +static int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target, + u8 index, u8 reg, const void *buf, u8 size) +{ + size_t dwords = DIV_ROUND_UP(size, 4); + int ret; + u32 val; + + if (!port->cap_usb4) + return -EINVAL; + + if (buf) { + ret = usb4_port_write_data(port, buf, dwords); + if (ret) + return ret; + } + + val = reg; + val |= size << PORT_CS_1_LENGTH_SHIFT; + val |= PORT_CS_1_WNR_WRITE; + val |= (target << PORT_CS_1_TARGET_SHIFT) & PORT_CS_1_TARGET_MASK; + if (target == USB4_SB_TARGET_RETIMER) + val |= (index << PORT_CS_1_RETIMER_INDEX_SHIFT); + val |= PORT_CS_1_PND; + + ret = tb_port_write(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_1, 1); + if (ret) + return ret; + + ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_1, + PORT_CS_1_PND, 0, 500); + if (ret) + return ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_usb4 + PORT_CS_1, 1); + if (ret) + return ret; + + if (val & PORT_CS_1_NR) + return -ENODEV; + if (val & PORT_CS_1_RC) + return -EIO; + + return 0; +} + +static int usb4_port_sb_op(struct tb_port *port, enum usb4_sb_target target, + u8 index, enum usb4_sb_opcode opcode, int timeout_msec) +{ + ktime_t timeout; + u32 val; + int ret; + + val = opcode; + ret = usb4_port_sb_write(port, target, index, USB4_SB_OPCODE, &val, + sizeof(val)); + if (ret) + return ret; + + timeout = ktime_add_ms(ktime_get(), timeout_msec); + + do { + /* Check results */ + ret = usb4_port_sb_read(port, target, index, USB4_SB_OPCODE, + &val, sizeof(val)); + if (ret) + return ret; + + switch (val) { + case 0: + return 0; + + case USB4_SB_OPCODE_ERR: + return -EAGAIN; + + case USB4_SB_OPCODE_ONS: + return -EOPNOTSUPP; + + default: + if (val != opcode) + return -EIO; + break; + } + } while (ktime_before(ktime_get(), timeout)); + + return -ETIMEDOUT; +} + +/** + * usb4_port_enumerate_retimers() - Send RT broadcast transaction + * @port: USB4 port + * + * This forces the USB4 port to send broadcast RT transaction which + * makes the retimers on the link to assign index to themselves. Returns + * %0 in case of success and negative errno if there was an error. + */ +int usb4_port_enumerate_retimers(struct tb_port *port) +{ + u32 val; + + val = USB4_SB_OPCODE_ENUMERATE_RETIMERS; + return usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0, + USB4_SB_OPCODE, &val, sizeof(val)); +} + +static inline int usb4_port_retimer_op(struct tb_port *port, u8 index, + enum usb4_sb_opcode opcode, + int timeout_msec) +{ + return usb4_port_sb_op(port, USB4_SB_TARGET_RETIMER, index, opcode, + timeout_msec); +} + +/** + * usb4_port_retimer_read() - Read from retimer sideband registers + * @port: USB4 port + * @index: Retimer index + * @reg: Sideband register to read + * @buf: Data from @reg is stored here + * @size: Number of bytes to read + * + * Function reads retimer sideband registers starting from @reg. The + * retimer is connected to @port at @index. Returns %0 in case of + * success, and read data is copied to @buf. If there is no retimer + * present at given @index returns %-ENODEV. In any other failure + * returns negative errno. + */ +int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf, + u8 size) +{ + return usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, reg, buf, + size); +} + +/** + * usb4_port_retimer_write() - Write to retimer sideband registers + * @port: USB4 port + * @index: Retimer index + * @reg: Sideband register to write + * @buf: Data that is written starting from @reg + * @size: Number of bytes to write + * + * Writes retimer sideband registers starting from @reg. The retimer is + * connected to @port at @index. Returns %0 in case of success. If there + * is no retimer present at given @index returns %-ENODEV. In any other + * failure returns negative errno. + */ +int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg, + const void *buf, u8 size) +{ + return usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, reg, buf, + size); +} + +/** + * usb4_port_retimer_is_last() - Is the retimer last on-board retimer + * @port: USB4 port + * @index: Retimer index + * + * If the retimer at @index is last one (connected directly to the + * Type-C port) this function returns %1. If it is not returns %0. If + * the retimer is not present returns %-ENODEV. Otherwise returns + * negative errno. + */ +int usb4_port_retimer_is_last(struct tb_port *port, u8 index) +{ + u32 metadata; + int ret; + + ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_LAST_RETIMER, + 500); + if (ret) + return ret; + + ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, &metadata, + sizeof(metadata)); + return ret ? ret : metadata & 1; +} + +/** + * usb4_port_retimer_nvm_sector_size() - Read retimer NVM sector size + * @port: USB4 port + * @index: Retimer index + * + * Reads NVM sector size (in bytes) of a retimer at @index. This + * operation can be used to determine whether the retimer supports NVM + * upgrade for example. Returns sector size in bytes or negative errno + * in case of error. Specifically returns %-ENODEV if there is no + * retimer at @index. + */ +int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) +{ + u32 metadata; + int ret; + + ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_GET_NVM_SECTOR_SIZE, + 500); + if (ret) + return ret; + + ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, &metadata, + sizeof(metadata)); + 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) +{ + u32 metadata, dwaddress; + int ret; + + dwaddress = address / 4; + metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) & + USB4_NVM_SET_OFFSET_MASK; + + ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata, + sizeof(metadata)); + if (ret) + return ret; + + return usb4_port_retimer_op(port, index, USB4_SB_OPCODE_NVM_SET_OFFSET, + 500); +} + +struct retimer_info { + struct tb_port *port; + u8 index; +}; + +static int usb4_port_retimer_nvm_write_next_block(void *data, const void *buf, + size_t dwords) + +{ + const struct retimer_info *info = data; + struct tb_port *port = info->port; + u8 index = info->index; + int ret; + + ret = usb4_port_retimer_write(port, index, USB4_SB_DATA, + buf, dwords * 4); + if (ret) + return ret; + + return usb4_port_retimer_op(port, index, + USB4_SB_OPCODE_NVM_BLOCK_WRITE, 1000); +} + +/** + * usb4_port_retimer_nvm_write() - Write to retimer NVM + * @port: USB4 port + * @index: Retimer index + * @address: Byte address where to start the write + * @buf: Data to write + * @size: Size in bytes how much to write + * + * Writes @size bytes from @buf to the retimer NVM. Used for NVM + * upgrade. Returns %0 if the data was written successfully and negative + * errno in case of failure. Specifically returns %-ENODEV if there is + * no retimer at @index. + */ +int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address, + const void *buf, size_t size) +{ + struct retimer_info info = { .port = port, .index = index }; + int ret; + + ret = usb4_port_retimer_nvm_set_offset(port, index, address); + if (ret) + return ret; + + return usb4_do_write_data(address, buf, size, + usb4_port_retimer_nvm_write_next_block, &info); +} + +/** + * usb4_port_retimer_nvm_authenticate() - Start retimer NVM upgrade + * @port: USB4 port + * @index: Retimer index + * + * After the new NVM image has been written via usb4_port_retimer_nvm_write() + * this function can be used to trigger the NVM upgrade process. If + * successful the retimer restarts with the new NVM and may not have the + * index set so one needs to call usb4_port_enumerate_retimers() to + * force index to be assigned. + */ +int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index) +{ + u32 val; + + /* + * We need to use the raw operation here because once the + * authentication completes the retimer index is not set anymore + * so we do not get back the status now. + */ + val = USB4_SB_OPCODE_NVM_AUTH_WRITE; + return usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, + USB4_SB_OPCODE, &val, sizeof(val)); +} + +/** + * usb4_port_retimer_nvm_authenticate_status() - Read status of NVM upgrade + * @port: USB4 port + * @index: Retimer index + * @status: Raw status code read from metadata + * + * This can be called after usb4_port_retimer_nvm_authenticate() and + * usb4_port_enumerate_retimers() to fetch status of the NVM upgrade. + * + * Returns %0 if the authentication status was successfully read. The + * completion metadata (the result) is then stored into @status. If + * reading the status fails, returns negative errno. + */ +int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index, + u32 *status) +{ + u32 metadata, val; + int ret; + + ret = usb4_port_retimer_read(port, index, USB4_SB_OPCODE, &val, + sizeof(val)); + if (ret) + return ret; + + switch (val) { + case 0: + *status = 0; + return 0; + + case USB4_SB_OPCODE_ERR: + ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, + &metadata, sizeof(metadata)); + if (ret) + return ret; + + *status = metadata & USB4_SB_METADATA_NVM_AUTH_WRITE_MASK; + return 0; + + case USB4_SB_OPCODE_ONS: + return -EOPNOTSUPP; + + default: + return -EIO; + } +} + +static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress, + void *buf, size_t dwords) +{ + const struct retimer_info *info = data; + struct tb_port *port = info->port; + u8 index = info->index; + u32 metadata; + int ret; + + metadata = dwaddress << USB4_NVM_READ_OFFSET_SHIFT; + if (dwords < USB4_DATA_DWORDS) + metadata |= dwords << USB4_NVM_READ_LENGTH_SHIFT; + + ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata, + sizeof(metadata)); + if (ret) + return ret; + + ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_NVM_READ, 500); + if (ret) + return ret; + + return usb4_port_retimer_read(port, index, USB4_SB_DATA, buf, + dwords * 4); +} + +/** + * usb4_port_retimer_nvm_read() - Read contents of retimer NVM + * @port: USB4 port + * @index: Retimer index + * @address: NVM address (in bytes) to start reading + * @buf: Data read from NVM is stored here + * @size: Number of bytes to read + * + * Reads retimer NVM and copies the contents to @buf. Returns %0 if the + * read was successful and negative errno in case of failure. + * Specifically returns %-ENODEV if there is no retimer at @index. + */ +int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index, + unsigned int address, void *buf, size_t size) +{ + struct retimer_info info = { .port = port, .index = index }; + + return usb4_do_read_data(address, buf, size, + usb4_port_retimer_nvm_read_block, &info); +} + /** * usb4_usb3_port_max_link_rate() - Maximum support USB3 link rate * @port: USB3 adapter port From patchwork Tue Jun 16 13:56:17 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mika Westerberg X-Patchwork-Id: 11607605 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id C2B3D13A0 for ; Tue, 16 Jun 2020 14:04:32 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A761320882 for ; Tue, 16 Jun 2020 14:04:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729366AbgFPOEb (ORCPT ); Tue, 16 Jun 2020 10:04:31 -0400 Received: from mga14.intel.com ([192.55.52.115]:36218 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729109AbgFPOE3 (ORCPT ); Tue, 16 Jun 2020 10:04:29 -0400 IronPort-SDR: YdaiwlB5Ywa5mOSAwtSjNJKam/TkjY8f2E8fUCdvd8/evRg+LGXk2Tna1HBsBES80/944APuEd RGTk3u7lfJLw== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga103.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 Jun 2020 06:56:25 -0700 IronPort-SDR: T7JWllKoSle3ECZlRfSC/Lc4PTe+SwhNjav8yTY3Xp99rLPCRhPw43ROrlCcp77dD50+RTfuTt L5D2O6GGOekQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.73,518,1583222400"; d="scan'208";a="420769365" Received: from black.fi.intel.com ([10.237.72.28]) by orsmga004.jf.intel.com with ESMTP; 16 Jun 2020 06:56:22 -0700 Received: by black.fi.intel.com (Postfix, from userid 1001) id 5CBE8946; Tue, 16 Jun 2020 16:56:18 +0300 (EEST) From: Mika Westerberg To: linux-usb@vger.kernel.org Cc: Andreas Noever , Michael Jamet , Mika Westerberg , Yehezkel Bernat , Kranthi Kuntala , Rajmohan Mani , Mario.Limonciello@dell.com, Greg Kroah-Hartman , Lukas Wunner Subject: [PATCH 6/6] thunderbolt: Add support for on-board retimers Date: Tue, 16 Jun 2020 16:56:17 +0300 Message-Id: <20200616135617.85752-7-mika.westerberg@linux.intel.com> X-Mailer: git-send-email 2.27.0.rc2 In-Reply-To: <20200616135617.85752-1-mika.westerberg@linux.intel.com> References: <20200616135617.85752-1-mika.westerberg@linux.intel.com> MIME-Version: 1.0 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: Kranthi Kuntala USB4 spec specifies standard access to retimers (both on-board and cable) through USB4 port sideband access. This makes it possible to upgrade their firmware in the same way than we already do with the routers. This enumerates on-board retimers under each USB4 port when the link comes up and adds them to the bus under the router the retimer belongs to. Retimers are exposed in sysfs with name like :. where device is the router the retimer belongs to, port is the USB4 port the retimer is connected to and index is the retimer index under that port (starting from 1). This applies to the upstream USB4 port as well so if there is on-board retimer between the port and the router it is also added accordingly. At this time we do not add cable retimers but there is no techincal restriction to do so in the future if needed. It is not clear whether it makes sense to upgrade their firmwares and at least Thunderbolt 3 cables it has not been done outside of lab environments. The sysfs interface is made to follow the router NVM upgrade to make it easy to extend the existing userspace (fwupd) to handle these as well. Signed-off-by: Kranthi Kuntala Co-developed-by: Mika Westerberg Signed-off-by: Mika Westerberg --- .../ABI/testing/sysfs-bus-thunderbolt | 33 ++ Documentation/admin-guide/thunderbolt.rst | 11 +- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/retimer.c | 485 ++++++++++++++++++ drivers/thunderbolt/sb_regs.h | 2 + drivers/thunderbolt/switch.c | 3 + drivers/thunderbolt/tb.c | 10 + drivers/thunderbolt/tb.h | 38 ++ 8 files changed, 578 insertions(+), 6 deletions(-) create mode 100644 drivers/thunderbolt/retimer.c diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 82e80de78dd0..bd504ed323e8 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -236,3 +236,36 @@ KernelVersion: 4.15 Contact: thunderbolt-software@lists.01.org Description: This contains XDomain service specific settings as bitmask. Format: %x + +What: /sys/bus/thunderbolt/devices/:./device +Date: Oct 2020 +KernelVersion: v5.9 +Contact: Mika Westerberg +Description: Retimer device identifier read from the hardware. + +What: /sys/bus/thunderbolt/devices/:./nvm_authenticate +Date: Oct 2020 +KernelVersion: v5.9 +Contact: Mika Westerberg +Description: When new NVM image is written to the non-active NVM + area (through non_activeX NVMem device), the + authentication procedure is started by writing 1 to + this file. If everything goes well, the device is + restarted with the new NVM firmware. If the image + verification fails an error code is returned instead. + + When read holds status of the last authentication + operation if an error occurred during the process. + Format: %x. + +What: /sys/bus/thunderbolt/devices/:./nvm_version +Date: Oct 2020 +KernelVersion: v5.9 +Contact: Mika Westerberg +Description: Holds retimer NVM version number. Format: %x.%x, major.minor. + +What: /sys/bus/thunderbolt/devices/:./vendor +Date: Oct 2020 +KernelVersion: v5.9 +Contact: Mika Westerberg +Description: Retimer vendor identifier read from the hardware. diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 10c4f0ce2ad0..613cb24c76c7 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -173,8 +173,8 @@ following ``udev`` rule:: ACTION=="add", SUBSYSTEM=="thunderbolt", ATTRS{iommu_dma_protection}=="1", ATTR{authorized}=="0", ATTR{authorized}="1" -Upgrading NVM on Thunderbolt device or host -------------------------------------------- +Upgrading NVM on Thunderbolt device, host or retimer +---------------------------------------------------- Since most of the functionality is handled in firmware running on a host controller or a device, it is important that the firmware can be upgraded to the latest where possible bugs in it have been fixed. @@ -185,9 +185,10 @@ for some machines: `Thunderbolt Updates `_ -Before you upgrade firmware on a device or host, please make sure it is a -suitable upgrade. Failing to do that may render the device (or host) in a -state where it cannot be used properly anymore without special tools! +Before you upgrade firmware on a device, host or retimer, please make +sure it is a suitable upgrade. Failing to do that may render the device +in a state where it cannot be used properly anymore without special +tools! Host NVM upgrade on Apple Macs is not supported. diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 7ee257cee7ff..cf7e1b42f4ad 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -2,6 +2,6 @@ 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 +thunderbolt-objs += nvm.o retimer.o obj-${CONFIG_USB4_KUNIT_TEST} += test.o diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c new file mode 100644 index 000000000000..620bcf586ee2 --- /dev/null +++ b/drivers/thunderbolt/retimer.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Thunderbolt/USB4 retimer support. + * + * Copyright (C) 2020, Intel Corporation + * Authors: Kranthi Kuntala + * Mika Westerberg + */ + +#include +#include +#include + +#include "sb_regs.h" +#include "tb.h" + +#define TB_MAX_RETIMER_INDEX 6 + +static int tb_retimer_nvm_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct tb_nvm *nvm = priv; + struct tb_retimer *rt = tb_to_retimer(nvm->dev); + int ret; + + pm_runtime_get_sync(&rt->dev); + + if (!mutex_trylock(&rt->tb->lock)) { + ret = restart_syscall(); + goto out; + } + + ret = usb4_port_retimer_nvm_read(rt->port, rt->index, offset, val, bytes); + mutex_unlock(&rt->tb->lock); + +out: + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_put_autosuspend(&rt->dev); + + return ret; +} + +static int tb_retimer_nvm_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct tb_nvm *nvm = priv; + struct tb_retimer *rt = tb_to_retimer(nvm->dev); + int ret = 0; + + if (!mutex_trylock(&rt->tb->lock)) + return restart_syscall(); + + ret = tb_nvm_write_buf(nvm, offset, val, bytes); + mutex_unlock(&rt->tb->lock); + + return ret; +} + +static int tb_retimer_nvm_add(struct tb_retimer *rt) +{ + struct tb_nvm *nvm; + u32 val, nvm_size; + int ret; + + nvm = tb_nvm_alloc(&rt->dev); + if (IS_ERR(nvm)) + return PTR_ERR(nvm); + + ret = usb4_port_retimer_nvm_read(rt->port, rt->index, NVM_VERSION, &val, + sizeof(val)); + if (ret) + goto err_nvm; + + nvm->major = val >> 16; + nvm->minor = val >> 8; + + ret = usb4_port_retimer_nvm_read(rt->port, rt->index, NVM_FLASH_SIZE, + &val, sizeof(val)); + if (ret) + goto err_nvm; + + nvm_size = (SZ_1M << (val & 7)) / 8; + nvm_size = (nvm_size - SZ_16K) / 2; + + ret = tb_nvm_add_active(nvm, nvm_size, tb_retimer_nvm_read); + if (ret) + goto err_nvm; + + ret = tb_nvm_add_non_active(nvm, NVM_MAX_SIZE, tb_retimer_nvm_write); + if (ret) + goto err_nvm; + + rt->nvm = nvm; + return 0; + +err_nvm: + tb_nvm_free(nvm); + return ret; +} + +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; + + image_size = rt->nvm->buf_data_size; + if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE) + return -EINVAL; + + /* + * FARB pointer must point inside the image and must at least + * contain parts of the digital section we will be reading here. + */ + hdr_size = (*(u32 *)buf) & 0xffffff; + if (hdr_size + NVM_DEVID + 2 >= image_size) + return -EINVAL; + + /* Digital section start should be aligned to 4k page */ + if (!IS_ALIGNED(hdr_size, SZ_4K)) + return -EINVAL; + + /* + * Read digital section size and check that it also fits inside + * the image. + */ + ds_size = *(u16 *)(buf + hdr_size); + if (ds_size >= image_size) + return -EINVAL; + + /* + * Make sure the device ID in the image matches the retimer + * hardware. + */ + device = *(u16 *)(buf + hdr_size + NVM_DEVID); + if (device != rt->device) + return -EINVAL; + + /* Skip headers in the image */ + buf += hdr_size; + image_size -= hdr_size; + + return usb4_port_retimer_nvm_write(rt->port, rt->index, 0, buf, + image_size); +} + +static ssize_t device_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_retimer *rt = tb_to_retimer(dev); + + return sprintf(buf, "%#x\n", rt->device); +} +static DEVICE_ATTR_RO(device); + +static ssize_t nvm_authenticate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tb_retimer *rt = tb_to_retimer(dev); + int ret; + + if (!mutex_trylock(&rt->tb->lock)) + return restart_syscall(); + + if (!rt->nvm) + ret = -EAGAIN; + else + ret = sprintf(buf, "%#x\n", rt->auth_status); + + mutex_unlock(&rt->tb->lock); + + return ret; +} + +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; + + pm_runtime_get_sync(&rt->dev); + + if (!mutex_trylock(&rt->tb->lock)) { + ret = restart_syscall(); + goto exit_rpm; + } + + if (!rt->nvm) { + ret = -EAGAIN; + goto exit_unlock; + } + + ret = kstrtobool(buf, &val); + if (ret) + goto exit_unlock; + + /* Always clear status */ + rt->auth_status = 0; + + if (val) { + if (!rt->nvm->buf) { + ret = -EINVAL; + goto exit_unlock; + } + + 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: + mutex_unlock(&rt->tb->lock); +exit_rpm: + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_put_autosuspend(&rt->dev); + + if (ret) + return ret; + return count; +} +static DEVICE_ATTR_RW(nvm_authenticate); + +static ssize_t nvm_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tb_retimer *rt = tb_to_retimer(dev); + int ret; + + if (!mutex_trylock(&rt->tb->lock)) + return restart_syscall(); + + if (!rt->nvm) + ret = -EAGAIN; + else + ret = sprintf(buf, "%x.%x\n", rt->nvm->major, rt->nvm->minor); + + mutex_unlock(&rt->tb->lock); + return ret; +} +static DEVICE_ATTR_RO(nvm_version); + +static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_retimer *rt = tb_to_retimer(dev); + + return sprintf(buf, "%#x\n", rt->vendor); +} +static DEVICE_ATTR_RO(vendor); + +static struct attribute *retimer_attrs[] = { + &dev_attr_device.attr, + &dev_attr_nvm_authenticate.attr, + &dev_attr_nvm_version.attr, + &dev_attr_vendor.attr, + NULL +}; + +static const struct attribute_group retimer_group = { + .attrs = retimer_attrs, +}; + +static const struct attribute_group *retimer_groups[] = { + &retimer_group, + NULL +}; + +static void tb_retimer_release(struct device *dev) +{ + struct tb_retimer *rt = tb_to_retimer(dev); + + kfree(rt); +} + +struct device_type tb_retimer_type = { + .name = "thunderbolt_retimer", + .groups = retimer_groups, + .release = tb_retimer_release, +}; + +static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status) +{ + struct tb_retimer *rt; + u32 vendor, device; + int ret; + + if (!port->cap_usb4) + return -EINVAL; + + ret = usb4_port_retimer_read(port, index, USB4_SB_VENDOR_ID, &vendor, + sizeof(vendor)); + if (ret) { + if (ret != -ENODEV) + tb_port_warn(port, "failed read retimer VendorId: %d\n", ret); + return ret; + } + + ret = usb4_port_retimer_read(port, index, USB4_SB_PRODUCT_ID, &device, + sizeof(device)); + if (ret) { + if (ret != -ENODEV) + tb_port_warn(port, "failed read retimer ProductId: %d\n", ret); + return ret; + } + + if (vendor != PCI_VENDOR_ID_INTEL && vendor != 0x8087) { + tb_port_info(port, "retimer NVM format of vendor %#x is not supported\n", + vendor); + return -EOPNOTSUPP; + } + + /* + * Check that it supports NVM operations. If not then don't add + * the device at all. + */ + ret = usb4_port_retimer_nvm_sector_size(port, index); + if (ret < 0) + return ret; + + rt = kzalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) + return -ENOMEM; + + rt->index = index; + rt->vendor = vendor; + rt->device = device; + rt->auth_status = auth_status; + rt->port = port; + rt->tb = port->sw->tb; + + rt->dev.parent = &port->sw->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), + port->port, index); + + ret = device_register(&rt->dev); + if (ret) { + dev_err(&rt->dev, "failed to register retimer: %d\n", ret); + put_device(&rt->dev); + return ret; + } + + ret = tb_retimer_nvm_add(rt); + if (ret) { + dev_err(&rt->dev, "failed to add NVM devices: %d\n", ret); + device_del(&rt->dev); + return ret; + } + + dev_info(&rt->dev, "new retimer found, vendor=%#x device=%#x\n", + rt->vendor, rt->device); + + pm_runtime_no_callbacks(&rt->dev); + pm_runtime_set_active(&rt->dev); + pm_runtime_enable(&rt->dev); + pm_runtime_set_autosuspend_delay(&rt->dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_use_autosuspend(&rt->dev); + + return 0; +} + +static void tb_retimer_remove(struct tb_retimer *rt) +{ + dev_info(&rt->dev, "retimer disconnected\n"); + tb_nvm_free(rt->nvm); + device_unregister(&rt->dev); +} + +struct tb_retimer_lookup { + const struct tb_port *port; + u8 index; +}; + +static int retimer_match(struct device *dev, void *data) +{ + const struct tb_retimer_lookup *lookup = data; + struct tb_retimer *rt = tb_to_retimer(dev); + + return rt && rt->port == lookup->port && rt->index == lookup->index; +} + +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); + if (dev) + return tb_to_retimer(dev); + + return NULL; +} + +/** + * tb_retimer_scan() - Scan for on-board retimers under port + * @port: USB4 port to scan + * + * 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. + */ +int tb_retimer_scan(struct tb_port *port) +{ + u32 status[TB_MAX_RETIMER_INDEX] = {}; + 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. + */ + ret = usb4_port_enumerate_retimers(port); + if (ret) + return ret; + + /* + * Before doing anything else, read the authentication status. + * If the retimer has it set, store it for the new retimer + * device instance. + */ + for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) + usb4_port_retimer_nvm_authenticate_status(port, i, &status[i]); + + for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++) { + /* + * Last retimer is true only for the last on-board + * retimer (the one connected directly to the Type-C + * port). + */ + ret = usb4_port_retimer_is_last(port, i); + if (ret > 0) + last_idx = i; + else if (ret < 0) + break; + } + + if (!last_idx) + return 0; + + /* Add on-board retimers if they do not exist already */ + for (i = 1; i <= last_idx; i++) { + struct tb_retimer *rt; + + rt = tb_port_find_retimer(port, i); + if (rt) { + put_device(&rt->dev); + } else { + ret = tb_retimer_add(port, i, status[i]); + if (ret && ret != -EOPNOTSUPP) + return ret; + } + } + + return 0; +} + +static int remove_retimer(struct device *dev, void *data) +{ + struct tb_retimer *rt = tb_to_retimer(dev); + struct tb_port *port = data; + + if (rt && rt->port == port) + tb_retimer_remove(rt); + return 0; +} + +/** + * tb_retimer_remove_all() - Remove all retimers under port + * @port: USB4 port whose retimers to remove + * + * This removes all previously added retimers under @port. + */ +void tb_retimer_remove_all(struct tb_port *port) +{ + if (port->cap_usb4) + device_for_each_child_reverse(&port->sw->dev, port, + remove_retimer); +} diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h index 0e587b7b9200..9dafd696612f 100644 --- a/drivers/thunderbolt/sb_regs.h +++ b/drivers/thunderbolt/sb_regs.h @@ -10,6 +10,8 @@ #ifndef _SB_REGS #define _SB_REGS +#define USB4_SB_VENDOR_ID 0x00 +#define USB4_SB_PRODUCT_ID 0x01 #define USB4_SB_OPCODE 0x08 enum usb4_sb_opcode { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c8ed614f14e6..817c66c7adcf 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2392,6 +2392,9 @@ void tb_switch_remove(struct tb_switch *sw) tb_xdomain_remove(port->xdomain); port->xdomain = NULL; } + + /* Remove any downstream retimers */ + tb_retimer_remove_all(port); } if (!sw->is_unplugged) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index bbcf0f25617c..f507815040eb 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -539,6 +539,9 @@ static void tb_scan_port(struct tb_port *port) tb_port_dbg(port, "port already has a remote\n"); return; } + + tb_retimer_scan(port); + sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, tb_downstream_route(port)); if (IS_ERR(sw)) { @@ -595,6 +598,9 @@ static void tb_scan_port(struct tb_port *port) if (tb_enable_tmu(sw)) tb_sw_warn(sw, "failed to enable TMU\n"); + /* Scan upstream retimers */ + tb_retimer_scan(upstream_port); + /* * Create USB 3.x tunnels only when the switch is plugged to the * domain. This is because we scan the domain also during discovery @@ -674,6 +680,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw) continue; if (port->remote->sw->is_unplugged) { + tb_retimer_remove_all(port); tb_remove_dp_resources(port->remote->sw); tb_switch_lane_bonding_disable(port->remote->sw); tb_switch_remove(port->remote->sw); @@ -1039,6 +1046,8 @@ static void tb_handle_hotplug(struct work_struct *work) goto put_sw; } if (ev->unplug) { + tb_retimer_remove_all(port); + if (tb_port_has_remote(port)) { tb_port_dbg(port, "switch unplugged\n"); tb_sw_set_unplugged(port->remote->sw); @@ -1283,6 +1292,7 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw) if (tb_is_upstream_port(port)) continue; if (port->xdomain && port->xdomain->is_unplugged) { + tb_retimer_remove_all(port); tb_xdomain_remove(port->xdomain); port->xdomain = NULL; ret++; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 8f840148378a..736d1589c31e 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -210,6 +210,28 @@ struct tb_port { struct list_head list; }; +/** + * tb_retimer: Thunderbolt retimer + * @dev: Device for the retimer + * @tb: Pointer to the domain the retimer belongs to + * @index: Retimer index facing the router USB4 port + * @vendor: Vendor ID of the retimer + * @device: Device ID of the retimer + * @port: Pointer to the lane 0 adapter + * @nvm: Pointer to the NVM if the retimer has one (%NULL otherwise) + * @auth_status: Status of last NVM authentication + */ +struct tb_retimer { + struct device dev; + struct tb *tb; + u8 index; + u32 vendor; + u32 device; + struct tb_port *port; + struct tb_nvm *nvm; + u32 auth_status; +}; + /** * struct tb_path_hop - routing information for a tb_path * @in_port: Ingress port of a switch @@ -553,6 +575,7 @@ struct tb *icm_probe(struct tb_nhi *nhi); 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; int tb_domain_init(void); @@ -853,6 +876,21 @@ 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); +void tb_retimer_remove_all(struct tb_port *port); + +static inline bool tb_is_retimer(const struct device *dev) +{ + return dev->type == &tb_retimer_type; +} + +static inline struct tb_retimer *tb_to_retimer(struct device *dev) +{ + if (tb_is_retimer(dev)) + return container_of(dev, struct tb_retimer, dev); + return NULL; +} + int usb4_switch_setup(struct tb_switch *sw); int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid); int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,