Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig USB4
tristate "Unified support for USB4 and Thunderbolt"
depends on PCI
select APPLE_PROPERTIES if EFI_STUB && X86
select CRC32
select CRYPTO_LIB_SHA256
select CRYPTO_LIB_UTILS
select NVMEM
help
USB4 and Thunderbolt driver. USB4 is the public specification
based on the Thunderbolt 3 protocol. This driver is required if
you want to hotplug Thunderbolt and USB4 compliant devices on
Apple hardware or on PCs with Intel Falcon Ridge or newer.
To compile this driver a module, choose M here. The module will be
called thunderbolt.
if USB4
config USB4_CONFIGFS
def_tristate USB4
depends on CONFIGFS_FS && !(USB4=y && CONFIGFS_FS=m)
config USB4_DEBUGFS_WRITE
bool "Enable write by debugfs to configuration spaces (DANGEROUS)"
help
Enables writing to device configuration registers through
debugfs interface. You can use tools such as Thunderbolt/USB4
debugging tools to access these registers. For more
information see:
https://github.com/intel/tbtools
Only enable this if you know what you are doing! Never enable
this for production systems or distro kernels.
config USB4_DEBUGFS_MARGINING
bool "Expose receiver lane margining operations under USB4 ports and retimers (DANGEROUS)"
depends on DEBUG_FS
depends on USB4_DEBUGFS_WRITE
help
Enables hardware and software based receiver lane margining
support under each USB4 port and retimer, including retimers
on the other side of the cable. Used for electrical quality
and robustness validation during manufacturing. Should not be
enabled by distro kernels.
config USB4_KUNIT_TEST
bool "KUnit tests" if !KUNIT_ALL_TESTS
depends on USB4 && KUNIT=y
default KUNIT_ALL_TESTS
config USB4_DMA_TEST
tristate "DMA traffic test driver"
depends on DEBUG_FS
help
This allows sending and receiving DMA traffic through loopback
connection. Loopback connection can be done by either special
dongle that has TX/RX lines crossed, or by simply connecting a
cable back to the host. Only enable this if you know what you
are doing. Normal users and distro kernels should say N here.
To compile this driver a module, choose M here. The module will be
called thunderbolt_dma_test.
config USB4_STREAM
tristate "Stream data over Thunderbolt/USB4 cable"
depends on USB4_CONFIGFS
help
This adds support for USB4STREAM protocol that allows two
hosts to stream data directly over Thunderbolt/USB4 cable
through /dev/tbstreamX devices.
To compile this driver a module, choose M here. The module will be
called thunderbolt_stream.
endif # USB4
+18
View File
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y := -I$(src)
obj-${CONFIG_USB4} := thunderbolt.o
thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o pci.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 += usb4_port.o nvm.o retimer.o quirks.o clx.o
thunderbolt-${CONFIG_ACPI} += acpi.o
thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
thunderbolt-$(CONFIG_USB4_CONFIGFS) += configfs.o
thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o
CFLAGS_test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
thunderbolt_dma_test-${CONFIG_USB4_DMA_TEST} += dma_test.o
obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o
thunderbolt_stream-${CONFIG_USB4_STREAM} += stream.o
obj-$(CONFIG_USB4_STREAM) += thunderbolt_stream.o
+368
View File
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACPI support
*
* Copyright (C) 2020, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/acpi.h>
#include <linux/pm_runtime.h>
#include "tb.h"
static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
void **ret)
{
struct acpi_device *adev = acpi_fetch_acpi_dev(handle);
struct fwnode_handle *fwnode;
struct tb_nhi *nhi = data;
struct pci_dev *pdev;
struct device *dev;
if (!adev)
return AE_OK;
fwnode = fwnode_find_reference(acpi_fwnode_handle(adev), "usb4-host-interface", 0);
if (IS_ERR(fwnode))
return AE_OK;
/* It needs to reference this NHI */
if (dev_fwnode(nhi->dev) != fwnode)
goto out_put;
/*
* Ignore USB3 ports here as USB core will set up device links between
* tunneled USB3 devices and NHI host during USB device creation.
* USB3 ports might not even have a physical device yet if xHCI driver
* isn't bound yet.
*/
dev = acpi_get_first_physical_node(adev);
if (!dev || !dev_is_pci(dev))
goto out_put;
/* Check that this matches a PCIe root/downstream port. */
pdev = to_pci_dev(dev);
if (pci_is_pcie(pdev) &&
(pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT ||
pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM)) {
const struct device_link *link;
/*
* Make them both active first to make sure the NHI does
* not runtime suspend before the consumer. The
* pm_runtime_put() below then allows the consumer to
* runtime suspend again (which then allows NHI runtime
* suspend too now that the device link is established).
*/
pm_runtime_get_sync(&pdev->dev);
link = device_link_add(&pdev->dev, nhi->dev,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_RPM_ACTIVE |
DL_FLAG_PM_RUNTIME);
if (link) {
dev_dbg(nhi->dev, "created link from %s\n",
dev_name(&pdev->dev));
*(bool *)ret = true;
} else {
dev_warn(nhi->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
pm_runtime_put(&pdev->dev);
}
out_put:
fwnode_handle_put(fwnode);
return AE_OK;
}
/**
* tb_acpi_add_links() - Add device links based on ACPI description
* @nhi: Pointer to NHI
*
* Goes over ACPI namespace finding tunneled ports that reference to
* @nhi ACPI node. For each reference a device link is added. The link
* is automatically removed by the driver core.
*
* Returns %true if at least one link was created, %false otherwise.
*/
bool tb_acpi_add_links(struct tb_nhi *nhi)
{
acpi_status status;
bool ret = false;
if (!has_acpi_companion(nhi->dev))
return false;
/*
* Find all devices that have usb4-host-controller interface
* property that references to this NHI.
*/
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32,
tb_acpi_add_link, NULL, nhi, (void **)&ret);
if (ACPI_FAILURE(status)) {
dev_warn(nhi->dev, "failed to enumerate tunneled ports\n");
return false;
}
return ret;
}
/**
* tb_acpi_is_native() - Did the platform grant native TBT/USB4 control
*
* Return: %true if the platform granted OS native control over
* TBT/USB4, %false otherwise.
*
* When returned %true, software based connection manager can be used,
* otherwise there is firmware based connection manager running.
*/
bool tb_acpi_is_native(void)
{
return osc_sb_native_usb4_support_confirmed &&
osc_sb_native_usb4_control;
}
/**
* tb_acpi_may_tunnel_usb3() - Is USB3 tunneling allowed by the platform
*
* Return: %true if software based connection manager is used and
* platform allows native USB 3.x tunneling, %false otherwise.
*/
bool tb_acpi_may_tunnel_usb3(void)
{
if (tb_acpi_is_native())
return osc_sb_native_usb4_control & OSC_USB_USB3_TUNNELING;
return true;
}
/**
* tb_acpi_may_tunnel_dp() - Is DisplayPort tunneling allowed by the platform
*
* Return: %true if software based connection manager is used and
* platform allows native DP tunneling, %false otherwise.
*/
bool tb_acpi_may_tunnel_dp(void)
{
if (tb_acpi_is_native())
return osc_sb_native_usb4_control & OSC_USB_DP_TUNNELING;
return true;
}
/**
* tb_acpi_may_tunnel_pcie() - Is PCIe tunneling allowed by the platform
*
* Return: %true if software based connection manager is used and
* platform allows native PCIe tunneling, %false otherwise.
*/
bool tb_acpi_may_tunnel_pcie(void)
{
if (tb_acpi_is_native())
return osc_sb_native_usb4_control & OSC_USB_PCIE_TUNNELING;
return true;
}
/**
* tb_acpi_is_xdomain_allowed() - Are XDomain connections allowed
*
* Return: %true if software based connection manager is used and
* platform allows XDomain tunneling, %false otherwise.
*/
bool tb_acpi_is_xdomain_allowed(void)
{
if (tb_acpi_is_native())
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.
*
* Return: %0 on success, negative errno otherwise.
*/
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.
*
* Return: %0 on success, negative errno otherwise.
*/
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_switch_find_companion(struct tb_switch *sw)
{
struct tb_switch *parent_sw = tb_switch_parent(sw);
struct acpi_device *adev = NULL;
/*
* Device routers exists under the downstream facing USB4 port
* of the parent router. Their _ADR is always 0.
*/
if (parent_sw) {
struct tb_port *port = tb_switch_downstream_port(sw);
struct acpi_device *port_adev;
port_adev = acpi_find_child_by_adr(ACPI_COMPANION(&parent_sw->dev),
port->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->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));
if (tb_is_usb4_port_device(dev))
return acpi_find_child_by_adr(ACPI_COMPANION(dev->parent),
tb_to_usb4_port_device(dev)->port->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);
}
+256
View File
@@ -0,0 +1,256 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt driver - capabilities lookup
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#include <linux/slab.h>
#include <linux/errno.h>
#include "tb.h"
#define CAP_OFFSET_MAX 0xff
#define VSE_CAP_OFFSET_MAX 0xffff
#define TMU_ACCESS_EN BIT(20)
static int tb_port_enable_tmu(struct tb_port *port, bool enable)
{
struct tb_switch *sw = port->sw;
u32 value, offset;
int ret;
/*
* Legacy devices need to have TMU access enabled before port
* space can be fully accessed.
*/
if (tb_switch_is_light_ridge(sw))
offset = 0x26;
else if (tb_switch_is_eagle_ridge(sw))
offset = 0x2a;
else
return 0;
ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1);
if (ret)
return ret;
if (enable)
value |= TMU_ACCESS_EN;
else
value &= ~TMU_ACCESS_EN;
return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1);
}
static void tb_port_dummy_read(struct tb_port *port)
{
/*
* When reading from next capability pointer location in port
* config space the read data is not cleared on LR. To avoid
* reading stale data on next read perform one dummy read after
* port capabilities are walked.
*/
if (tb_switch_is_light_ridge(port->sw)) {
u32 dummy;
tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1);
}
}
/**
* tb_port_next_cap() - Return next capability in the linked list
* @port: Port to find the capability for
* @offset: Previous capability offset (%0 for start)
*
* Finds dword offset of the next capability in port config space
* capability list. When passed %0 in @offset parameter, first entry
* will be returned, if it exists.
*
* Return:
* * Double word offset of the first or next capability - On success.
* * %0 - If no next capability is found.
* * Negative errno - Another error occurred.
*/
int tb_port_next_cap(struct tb_port *port, unsigned int offset)
{
struct tb_cap_any header;
int ret;
if (!offset)
return port->config.first_cap_offset;
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
if (ret)
return ret;
return header.basic.next;
}
static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
{
int offset = 0;
do {
struct tb_cap_any header;
int ret;
offset = tb_port_next_cap(port, offset);
if (offset < 0)
return offset;
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
} while (offset > 0);
return -ENOENT;
}
/**
* tb_port_find_cap() - Find port capability
* @port: Port to find the capability for
* @cap: Capability to look
*
* Return:
* * Offset to the start of capability - On success.
* * %-ENOENT - If no such capability was found.
* * Negative errno - Another error occurred.
*/
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
{
int ret;
ret = tb_port_enable_tmu(port, true);
if (ret)
return ret;
ret = __tb_port_find_cap(port, cap);
tb_port_dummy_read(port);
tb_port_enable_tmu(port, false);
return ret;
}
/**
* tb_switch_next_cap() - Return next capability in the linked list
* @sw: Switch to find the capability for
* @offset: Previous capability offset (%0 for start)
*
* Finds dword offset of the next capability in port config space
* capability list. When passed %0 in @offset parameter, first entry
* will be returned, if it exists.
*
* Return:
* * Double word offset of the first or next capability - On success.
* * %0 - If no next capability is found.
* * Negative errno - Another error occurred.
*/
int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
{
struct tb_cap_any header;
int ret;
if (!offset)
return sw->config.first_cap_offset;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
if (ret)
return ret;
switch (header.basic.cap) {
case TB_SWITCH_CAP_TMU:
ret = header.basic.next;
break;
case TB_SWITCH_CAP_VSE:
if (!header.extended_short.length)
ret = header.extended_long.next;
else
ret = header.extended_short.next;
break;
default:
tb_sw_dbg(sw, "unknown capability %#x at %#x\n",
header.basic.cap, offset);
ret = -EINVAL;
break;
}
return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret;
}
/**
* tb_switch_find_cap() - Find switch capability
* @sw: Switch to find the capability for
* @cap: Capability to look
*
* Return:
* * Offset to the start of capability - On success.
* * %-ENOENT - If no such capability was found.
* * Negative errno - Another error occurred.
*/
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
{
int offset = 0;
do {
struct tb_cap_any header;
int ret;
offset = tb_switch_next_cap(sw, offset);
if (offset < 0)
return offset;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
} while (offset);
return -ENOENT;
}
/**
* tb_switch_find_vse_cap() - Find switch vendor specific capability
* @sw: Switch to find the capability for
* @vsec: Vendor specific capability to look
*
* This function enumerates vendor specific capabilities (VSEC) of a
* switch and returns offset when capability matching @vsec is found.
*
* Return:
* * Offset of capability - On success.
* * %-ENOENT - If capability was not found.
* * Negative errno - Another error occurred.
*/
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
{
int offset = 0;
do {
struct tb_cap_any header;
int ret;
offset = tb_switch_next_cap(sw, offset);
if (offset < 0)
return offset;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
if (ret)
return ret;
if (header.extended_short.cap == TB_SWITCH_CAP_VSE &&
header.extended_short.vsec_id == vsec)
return offset;
} while (offset);
return -ENOENT;
}
+428
View File
@@ -0,0 +1,428 @@
// SPDX-License-Identifier: GPL-2.0
/*
* CLx support
*
* Copyright (C) 2020 - 2023, Intel Corporation
* Authors: Gil Fine <gil.fine@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/module.h>
#include "tb.h"
static bool clx_enabled = true;
module_param_named(clx, clx_enabled, bool, 0444);
MODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)");
static const char *clx_name(unsigned int clx)
{
switch (clx) {
case TB_CL0S | TB_CL1 | TB_CL2:
return "CL0s/CL1/CL2";
case TB_CL1 | TB_CL2:
return "CL1/CL2";
case TB_CL0S | TB_CL2:
return "CL0s/CL2";
case TB_CL0S | TB_CL1:
return "CL0s/CL1";
case TB_CL0S:
return "CL0s";
case 0:
return "disabled";
default:
return "unknown";
}
}
static int tb_port_pm_secondary_set(struct tb_port *port, bool secondary)
{
u32 phy;
int ret;
ret = tb_port_read(port, &phy, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
if (ret)
return ret;
if (secondary)
phy |= LANE_ADP_CS_1_PMS;
else
phy &= ~LANE_ADP_CS_1_PMS;
return tb_port_write(port, &phy, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
}
static int tb_port_pm_secondary_enable(struct tb_port *port)
{
return tb_port_pm_secondary_set(port, true);
}
static int tb_port_pm_secondary_disable(struct tb_port *port)
{
return tb_port_pm_secondary_set(port, false);
}
/* Called for USB4 or Titan Ridge routers only */
static bool tb_port_clx_supported(struct tb_port *port, unsigned int clx)
{
u32 val, mask = 0;
bool ret;
/* Don't enable CLx in case of two single-lane links */
if (!port->bonded && port->dual_link_port)
return false;
/* Don't enable CLx in case of inter-domain link */
if (port->xdomain)
return false;
if (tb_switch_is_usb4(port->sw)) {
if (!usb4_port_clx_supported(port))
return false;
} else if (!tb_lc_is_clx_supported(port)) {
return false;
}
if (clx & TB_CL0S)
mask |= LANE_ADP_CS_0_CL0S_SUPPORT;
if (clx & TB_CL1)
mask |= LANE_ADP_CS_0_CL1_SUPPORT;
if (clx & TB_CL2)
mask |= LANE_ADP_CS_0_CL2_SUPPORT;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_0, 1);
if (ret)
return false;
return !!(val & mask);
}
static int tb_port_clx_set(struct tb_port *port, unsigned int clx, bool enable)
{
u32 phy, mask = 0;
int ret;
if (clx & TB_CL0S)
mask |= LANE_ADP_CS_1_CL0S_ENABLE;
if (clx & TB_CL1)
mask |= LANE_ADP_CS_1_CL1_ENABLE;
if (clx & TB_CL2)
mask |= LANE_ADP_CS_1_CL2_ENABLE;
if (!mask)
return -EOPNOTSUPP;
ret = tb_port_read(port, &phy, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
if (ret)
return ret;
if (enable)
phy |= mask;
else
phy &= ~mask;
return tb_port_write(port, &phy, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
}
static int tb_port_clx_disable(struct tb_port *port, unsigned int clx)
{
return tb_port_clx_set(port, clx, false);
}
static int tb_port_clx_enable(struct tb_port *port, unsigned int clx)
{
return tb_port_clx_set(port, clx, true);
}
static int tb_port_clx(struct tb_port *port)
{
u32 val;
int ret;
if (!tb_port_clx_supported(port, TB_CL0S | TB_CL1 | TB_CL2))
return 0;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
if (ret)
return ret;
if (val & LANE_ADP_CS_1_CL0S_ENABLE)
ret |= TB_CL0S;
if (val & LANE_ADP_CS_1_CL1_ENABLE)
ret |= TB_CL1;
if (val & LANE_ADP_CS_1_CL2_ENABLE)
ret |= TB_CL2;
return ret;
}
/**
* tb_port_clx_is_enabled() - Is given CL state enabled
* @port: USB4 port to check
* @clx: Mask of CL states to check
*
* Return: %true if any of the given CL states is enabled for @port,
* %false otherwise.
*/
bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx)
{
return !!(tb_port_clx(port) & clx);
}
/**
* tb_switch_clx_is_supported() - Is CLx supported on this type of router
* @sw: The router to check CLx support for
*
* Return: %true if CLx is supported, %false otherwise.
*/
static bool tb_switch_clx_is_supported(const struct tb_switch *sw)
{
if (!clx_enabled)
return false;
if (sw->quirks & QUIRK_NO_CLX)
return false;
/*
* CLx is not enabled and validated on Intel USB4 platforms
* before Alder Lake.
*/
if (tb_switch_is_tiger_lake(sw))
return false;
return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
}
/**
* tb_switch_clx_init() - Initialize router CL states
* @sw: Router
*
* Can be called for any router. Initializes the current CL state by
* reading it from the hardware.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_switch_clx_init(struct tb_switch *sw)
{
struct tb_port *up, *down;
unsigned int clx, tmp;
if (tb_switch_is_icm(sw))
return 0;
if (!tb_route(sw))
return 0;
if (!tb_switch_clx_is_supported(sw))
return 0;
up = tb_upstream_port(sw);
down = tb_switch_downstream_port(sw);
clx = tb_port_clx(up);
tmp = tb_port_clx(down);
if (clx != tmp)
tb_sw_warn(sw, "CLx: inconsistent configuration %#x != %#x\n",
clx, tmp);
tb_sw_dbg(sw, "CLx: current mode: %s\n", clx_name(clx));
sw->clx = clx;
return 0;
}
static int tb_switch_pm_secondary_resolve(struct tb_switch *sw)
{
struct tb_port *up, *down;
int ret;
if (!tb_route(sw))
return 0;
up = tb_upstream_port(sw);
down = tb_switch_downstream_port(sw);
ret = tb_port_pm_secondary_enable(up);
if (ret)
return ret;
return tb_port_pm_secondary_disable(down);
}
static int tb_switch_mask_clx_objections(struct tb_switch *sw)
{
int up_port = sw->config.upstream_port_number;
u32 offset, val[2], mask_obj, unmask_obj;
int ret, i;
/* Only Titan Ridge of pre-USB4 devices support CLx states */
if (!tb_switch_is_titan_ridge(sw))
return 0;
if (!tb_route(sw))
return 0;
/*
* In Titan Ridge there are only 2 dual-lane Thunderbolt ports:
* Port A consists of lane adapters 1,2 and
* Port B consists of lane adapters 3,4
* If upstream port is A, (lanes are 1,2), we mask objections from
* port B (lanes 3,4) and unmask objections from Port A and vice-versa.
*/
if (up_port == 1) {
mask_obj = TB_LOW_PWR_C0_PORT_B_MASK;
unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK;
offset = TB_LOW_PWR_C1_CL1;
} else {
mask_obj = TB_LOW_PWR_C1_PORT_A_MASK;
unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK;
offset = TB_LOW_PWR_C3_CL1;
}
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
sw->cap_lp + offset, ARRAY_SIZE(val));
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(val); i++) {
val[i] |= mask_obj;
val[i] &= ~unmask_obj;
}
return tb_sw_write(sw, &val, TB_CFG_SWITCH,
sw->cap_lp + offset, ARRAY_SIZE(val));
}
static bool validate_mask(unsigned int clx)
{
/* Previous states need to be enabled */
if (clx & TB_CL1)
return (clx & TB_CL0S) == TB_CL0S;
return true;
}
/**
* tb_switch_clx_enable() - Enable CLx on upstream port of specified router
* @sw: Router to enable CLx for
* @clx: The CLx state to enable
*
* CLx is enabled only if both sides of the link support CLx, and if both sides
* of the link are not configured as two single lane links and only if the link
* is not inter-domain link. The complete set of conditions is described in CM
* Guide 1.0 section 8.1.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx)
{
bool up_clx_support, down_clx_support;
struct tb_switch *parent_sw;
struct tb_port *up, *down;
int ret;
if (!clx || sw->clx == clx)
return 0;
if (!validate_mask(clx))
return -EINVAL;
parent_sw = tb_switch_parent(sw);
if (!parent_sw)
return 0;
if (!tb_switch_clx_is_supported(parent_sw) ||
!tb_switch_clx_is_supported(sw))
return 0;
/* Only support CL2 for v2 routers */
if ((clx & TB_CL2) &&
(usb4_switch_version(parent_sw) < 2 ||
usb4_switch_version(sw) < 2))
return -EOPNOTSUPP;
ret = tb_switch_pm_secondary_resolve(sw);
if (ret)
return ret;
up = tb_upstream_port(sw);
down = tb_switch_downstream_port(sw);
up_clx_support = tb_port_clx_supported(up, clx);
down_clx_support = tb_port_clx_supported(down, clx);
tb_port_dbg(up, "CLx: %s %ssupported\n", clx_name(clx),
up_clx_support ? "" : "not ");
tb_port_dbg(down, "CLx: %s %ssupported\n", clx_name(clx),
down_clx_support ? "" : "not ");
if (!up_clx_support || !down_clx_support)
return -EOPNOTSUPP;
ret = tb_port_clx_enable(up, clx);
if (ret)
return ret;
ret = tb_port_clx_enable(down, clx);
if (ret) {
tb_port_clx_disable(up, clx);
return ret;
}
ret = tb_switch_mask_clx_objections(sw);
if (ret) {
tb_port_clx_disable(up, clx);
tb_port_clx_disable(down, clx);
return ret;
}
sw->clx |= clx;
tb_sw_dbg(sw, "CLx: %s enabled\n", clx_name(clx));
return 0;
}
/**
* tb_switch_clx_disable() - Disable CLx on upstream port of specified router
* @sw: Router to disable CLx for
*
* Disables all CL states of the given router. Can be called on any
* router and if the states were not enabled already does nothing.
*
* Return: CL states that were disabled or negative errno otherwise.
*/
int tb_switch_clx_disable(struct tb_switch *sw)
{
unsigned int clx = sw->clx;
struct tb_port *up, *down;
int ret;
if (!tb_switch_clx_is_supported(sw))
return 0;
if (!clx)
return 0;
if (sw->is_unplugged)
return clx;
up = tb_upstream_port(sw);
down = tb_switch_downstream_port(sw);
ret = tb_port_clx_disable(up, clx);
if (ret)
return ret;
ret = tb_port_clx_disable(down, clx);
if (ret)
return ret;
sw->clx = 0;
tb_sw_dbg(sw, "CLx: %s disabled\n", clx_name(clx));
return clx;
}
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ConfigFS support
*
* Copyright (C) 2026, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/configfs.h>
#include <linux/export.h>
#include "tb.h"
static const struct config_item_type tb_root_group_type = {
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem tb_configfs = {
.su_group = {
.cg_item = {
.ci_namebuf = "thunderbolt",
.ci_type = &tb_root_group_type,
},
},
};
/**
* tb_configfs_register_group() - Register Thunderbolt ConfigFS group
* @group: Group to register.
*
* Registers the new @group under Thunderbolt subsystem ConfigFS.
*
* Return: 0% in case of success, negative errno otherwise.
*/
int tb_configfs_register_group(struct config_group *group)
{
return configfs_register_group(&tb_configfs.su_group, group);
}
EXPORT_SYMBOL_GPL(tb_configfs_register_group);
/**
* tb_configfs_unregister_group() - Unregister previously registered group
* @group: Group to unregister.
*/
void tb_configfs_unregister_group(struct config_group *group)
{
configfs_unregister_group(group);
}
EXPORT_SYMBOL_GPL(tb_configfs_unregister_group);
int tb_configfs_init(void)
{
config_group_init(&tb_configfs.su_group);
mutex_init(&tb_configfs.su_mutex);
return configfs_register_subsystem(&tb_configfs);
}
void tb_configfs_exit(void)
{
configfs_unregister_subsystem(&tb_configfs);
}
File diff suppressed because it is too large Load Diff
+144
View File
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt driver - control channel and configuration commands
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#ifndef _TB_CFG
#define _TB_CFG
#include <linux/kref.h>
#include <linux/thunderbolt.h>
#include "nhi.h"
#include "tb_msgs.h"
/* control channel */
struct tb_ctl;
typedef bool (*event_cb)(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec,
event_cb cb, void *cb_data);
void tb_ctl_start(struct tb_ctl *ctl);
void tb_ctl_stop(struct tb_ctl *ctl);
void tb_ctl_free(struct tb_ctl *ctl);
/* configuration commands */
struct tb_cfg_result {
u64 response_route;
u32 response_port; /*
* If err = 1 then this is the port that send the
* error.
* If err = 0 and if this was a cfg_read/write then
* this is the upstream port of the responding
* switch.
* Otherwise the field is set to zero.
*/
int err; /* negative errors, 0 for success, 1 for tb errors */
enum tb_cfg_error tb_error; /* valid if err == 1 */
};
struct ctl_pkg {
struct tb_ctl *ctl;
void *buffer;
struct ring_frame frame;
};
/**
* struct tb_cfg_request - Control channel request
* @kref: Reference count
* @ctl: Pointer to the control channel structure. Only set when the
* request is queued.
* @request: Request is stored here
* @request_size: Size of the request packet (in bytes)
* @request_type: Type of the request packet
* @response: Response is stored here
* @response_size: Maximum size of one response packet
* @response_type: Expected type of the response packet
* @npackets: Number of packets expected to be returned with this request
* @match: Function used to match the incoming packet
* @copy: Function used to copy the incoming packet to @response
* @callback: Callback called when the request is finished successfully
* @callback_data: Data to be passed to @callback
* @flags: Flags for the request
* @work: Work item used to complete the request
* @result: Result after the request has been completed
* @list: Requests are queued using this field
*
* An arbitrary request over Thunderbolt control channel. For standard
* control channel message, one should use tb_cfg_read/write() and
* friends if possible.
*/
struct tb_cfg_request {
struct kref kref;
struct tb_ctl *ctl;
const void *request;
size_t request_size;
enum tb_cfg_pkg_type request_type;
void *response;
size_t response_size;
enum tb_cfg_pkg_type response_type;
size_t npackets;
bool (*match)(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg);
bool (*copy)(struct tb_cfg_request *req, const struct ctl_pkg *pkg);
void (*callback)(void *callback_data);
void *callback_data;
unsigned long flags;
struct work_struct work;
struct tb_cfg_result result;
struct list_head list;
};
#define TB_CFG_REQUEST_ACTIVE 0
#define TB_CFG_REQUEST_CANCELED 1
struct tb_cfg_request *tb_cfg_request_alloc(void);
void tb_cfg_request_get(struct tb_cfg_request *req);
void tb_cfg_request_put(struct tb_cfg_request *req);
int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
void (*callback)(void *), void *callback_data);
void tb_cfg_request_cancel(struct tb_cfg_request *req, int err);
struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
struct tb_cfg_request *req, int timeout_msec);
static inline u64 tb_cfg_get_route(const struct tb_cfg_header *header)
{
return (u64) header->route_hi << 32 | header->route_lo;
}
static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
{
struct tb_cfg_header header = {
.route_hi = route >> 32,
.route_lo = route,
};
/* check for overflow, route_hi is not 32 bits! */
WARN_ON(tb_cfg_get_route(&header) != route);
return header;
}
int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route,
const struct cfg_error_pkg *error);
int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route);
struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
u64 route, u32 port,
enum tb_cfg_space space, u32 offset,
u32 length, int timeout_msec);
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer,
u64 route, u32 port,
enum tb_cfg_space space, u32 offset,
u32 length, int timeout_msec);
int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length);
int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length);
int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route);
#endif
File diff suppressed because it is too large Load Diff
+460
View File
@@ -0,0 +1,460 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt DMA configuration based mailbox support
*
* Copyright (C) 2017, Intel Corporation
* Authors: Michael Jamet <michael.jamet@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include "dma_port.h"
#include "tb_regs.h"
#define DMA_PORT_CAP 0x3e
#define MAIL_DATA 1
#define MAIL_DATA_DWORDS 16
#define MAIL_IN 17
#define MAIL_IN_CMD_SHIFT 28
#define MAIL_IN_CMD_MASK GENMASK(31, 28)
#define MAIL_IN_CMD_FLASH_WRITE 0x0
#define MAIL_IN_CMD_FLASH_UPDATE_AUTH 0x1
#define MAIL_IN_CMD_FLASH_READ 0x2
#define MAIL_IN_CMD_POWER_CYCLE 0x4
#define MAIL_IN_DWORDS_SHIFT 24
#define MAIL_IN_DWORDS_MASK GENMASK(27, 24)
#define MAIL_IN_ADDRESS_SHIFT 2
#define MAIL_IN_ADDRESS_MASK GENMASK(23, 2)
#define MAIL_IN_CSS BIT(1)
#define MAIL_IN_OP_REQUEST BIT(0)
#define MAIL_OUT 18
#define MAIL_OUT_STATUS_RESPONSE BIT(29)
#define MAIL_OUT_STATUS_CMD_SHIFT 4
#define MAIL_OUT_STATUS_CMD_MASK GENMASK(7, 4)
#define MAIL_OUT_STATUS_MASK GENMASK(3, 0)
#define MAIL_OUT_STATUS_COMPLETED 0
#define MAIL_OUT_STATUS_ERR_AUTH 1
#define MAIL_OUT_STATUS_ERR_ACCESS 2
#define DMA_PORT_TIMEOUT 5000 /* ms */
#define DMA_PORT_RETRIES 3
/**
* struct tb_dma_port - DMA control port
* @sw: Switch the DMA port belongs to
* @port: Switch port number where DMA capability is found
* @base: Start offset of the mailbox registers
* @buf: Temporary buffer to store a single block
*/
struct tb_dma_port {
struct tb_switch *sw;
u8 port;
u32 base;
u8 buf[];
};
/*
* When the switch is in safe mode it supports very little functionality
* so we don't validate that much here.
*/
static bool dma_port_match(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg)
{
u64 route = tb_cfg_get_route(pkg->buffer) & ~BIT_ULL(63);
if (pkg->frame.eof == TB_CFG_PKG_ERROR)
return true;
if (pkg->frame.eof != req->response_type)
return false;
if (route != tb_cfg_get_route(req->request))
return false;
if (pkg->frame.size != req->response_size)
return false;
return true;
}
static bool dma_port_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
{
memcpy(req->response, pkg->buffer, req->response_size);
return true;
}
static int dma_port_read(struct tb_ctl *ctl, void *buffer, u64 route,
u32 port, u32 offset, u32 length, int timeout_msec)
{
struct cfg_read_pkg request = {
.header = tb_cfg_make_header(route),
.addr = {
.seq = 1,
.port = port,
.space = TB_CFG_PORT,
.offset = offset,
.length = length,
},
};
struct tb_cfg_request *req;
struct cfg_write_pkg reply;
struct tb_cfg_result res;
req = tb_cfg_request_alloc();
if (!req)
return -ENOMEM;
req->match = dma_port_match;
req->copy = dma_port_copy;
req->request = &request;
req->request_size = sizeof(request);
req->request_type = TB_CFG_PKG_READ;
req->response = &reply;
req->response_size = 12 + 4 * length;
req->response_type = TB_CFG_PKG_READ;
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
if (res.err)
return res.err;
memcpy(buffer, &reply.data, 4 * length);
return 0;
}
static int dma_port_write(struct tb_ctl *ctl, const void *buffer, u64 route,
u32 port, u32 offset, u32 length, int timeout_msec)
{
struct cfg_write_pkg request = {
.header = tb_cfg_make_header(route),
.addr = {
.seq = 1,
.port = port,
.space = TB_CFG_PORT,
.offset = offset,
.length = length,
},
};
struct tb_cfg_request *req;
struct cfg_read_pkg reply;
struct tb_cfg_result res;
memcpy(&request.data, buffer, length * 4);
req = tb_cfg_request_alloc();
if (!req)
return -ENOMEM;
req->match = dma_port_match;
req->copy = dma_port_copy;
req->request = &request;
req->request_size = 12 + 4 * length;
req->request_type = TB_CFG_PKG_WRITE;
req->response = &reply;
req->response_size = sizeof(reply);
req->response_type = TB_CFG_PKG_WRITE;
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
return res.err;
}
static int dma_find_port(struct tb_switch *sw)
{
static const int ports[] = { 3, 5, 7 };
int i;
/*
* The DMA (NHI) port is either 3, 5 or 7 depending on the
* controller. Try all of them.
*/
for (i = 0; i < ARRAY_SIZE(ports); i++) {
u32 type;
int ret;
ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), ports[i],
2, 1, DMA_PORT_TIMEOUT);
if (!ret && (type & 0xffffff) == TB_TYPE_NHI)
return ports[i];
}
return -ENODEV;
}
/**
* dma_port_alloc() - Finds DMA control port from a switch pointed by route
* @sw: Switch from where find the DMA port
*
* Function checks if the switch NHI port supports DMA configuration
* based mailbox capability and if it does, allocates and initializes
* DMA port structure. Returns %NULL if the capabity was not found.
*
* The DMA control port is functional also when the switch is in safe
* mode.
*
* Return: &struct tb_dma_port on success, %NULL otherwise.
*/
struct tb_dma_port *dma_port_alloc(struct tb_switch *sw)
{
struct tb_dma_port *dma;
int port;
port = dma_find_port(sw);
if (port < 0)
return NULL;
dma = kzalloc_flex(*dma, buf, MAIL_DATA_DWORDS);
if (!dma)
return NULL;
dma->sw = sw;
dma->port = port;
dma->base = DMA_PORT_CAP;
return dma;
}
/**
* dma_port_free() - Release DMA control port structure
* @dma: DMA control port
*/
void dma_port_free(struct tb_dma_port *dma)
{
kfree(dma);
}
static int dma_port_wait_for_completion(struct tb_dma_port *dma,
unsigned int timeout)
{
unsigned long end = jiffies + msecs_to_jiffies(timeout);
struct tb_switch *sw = dma->sw;
do {
int ret;
u32 in;
ret = dma_port_read(sw->tb->ctl, &in, tb_route(sw), dma->port,
dma->base + MAIL_IN, 1, 50);
if (ret) {
if (ret != -ETIMEDOUT)
return ret;
} else if (!(in & MAIL_IN_OP_REQUEST)) {
return 0;
}
usleep_range(50, 100);
} while (time_before(jiffies, end));
return -ETIMEDOUT;
}
static int status_to_errno(u32 status)
{
switch (status & MAIL_OUT_STATUS_MASK) {
case MAIL_OUT_STATUS_COMPLETED:
return 0;
case MAIL_OUT_STATUS_ERR_AUTH:
return -EINVAL;
case MAIL_OUT_STATUS_ERR_ACCESS:
return -EACCES;
}
return -EIO;
}
static int dma_port_request(struct tb_dma_port *dma, u32 in,
unsigned int timeout)
{
struct tb_switch *sw = dma->sw;
u32 out;
int ret;
ret = dma_port_write(sw->tb->ctl, &in, tb_route(sw), dma->port,
dma->base + MAIL_IN, 1, DMA_PORT_TIMEOUT);
if (ret)
return ret;
ret = dma_port_wait_for_completion(dma, timeout);
if (ret)
return ret;
ret = dma_port_read(sw->tb->ctl, &out, tb_route(sw), dma->port,
dma->base + MAIL_OUT, 1, DMA_PORT_TIMEOUT);
if (ret)
return ret;
return status_to_errno(out);
}
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;
int ret;
u32 in;
in = MAIL_IN_CMD_FLASH_READ << MAIL_IN_CMD_SHIFT;
if (dwords < MAIL_DATA_DWORDS)
in |= (dwords << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK;
in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK;
in |= MAIL_IN_OP_REQUEST;
ret = dma_port_request(dma, in, DMA_PORT_TIMEOUT);
if (ret)
return ret;
return dma_port_read(sw->tb->ctl, buf, tb_route(sw), dma->port,
dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT);
}
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;
int ret;
u32 in;
/* Write the block to MAIL_DATA registers */
ret = dma_port_write(sw->tb->ctl, buf, tb_route(sw), dma->port,
dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT);
if (ret)
return ret;
in = MAIL_IN_CMD_FLASH_WRITE << MAIL_IN_CMD_SHIFT;
/* CSS header write is always done to the same magic address */
if (dwaddress >= DMA_PORT_CSS_ADDRESS)
in |= MAIL_IN_CSS;
in |= ((dwords - 1) << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK;
in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK;
in |= MAIL_IN_OP_REQUEST;
return dma_port_request(dma, in, DMA_PORT_TIMEOUT);
}
/**
* dma_port_flash_read() - Read from active flash region
* @dma: DMA control port
* @address: Address relative to the start of active region
* @buf: Buffer where the data is read
* @size: Size of the buffer
*
* Return: %0 on success, negative errno otherwise.
*/
int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
void *buf, size_t size)
{
return tb_nvm_read_data(address, buf, size, DMA_PORT_RETRIES,
dma_port_flash_read_block, dma);
}
/**
* dma_port_flash_write() - Write to non-active flash region
* @dma: DMA control port
* @address: Address relative to the start of non-active region
* @buf: Data to write
* @size: Size of the buffer
*
* Writes block of data to the non-active flash region of the switch. If
* the address is given as %DMA_PORT_CSS_ADDRESS the block is written
* using CSS command.
*
* Return: %0 on success, negative errno otherwise.
*/
int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
const void *buf, size_t size)
{
if (address >= DMA_PORT_CSS_ADDRESS && size > DMA_PORT_CSS_MAX_SIZE)
return -E2BIG;
return tb_nvm_write_data(address, buf, size, DMA_PORT_RETRIES,
dma_port_flash_write_block, dma);
}
/**
* dma_port_flash_update_auth() - Starts flash authenticate cycle
* @dma: DMA control port
*
* Starts the flash update authentication cycle. If the image in the
* non-active area was valid, the switch starts upgrade process where
* active and non-active area get swapped in the end. Caller should call
* dma_port_flash_update_auth_status() to get status of this command.
* This is because if the switch in question is root switch the
* thunderbolt host controller gets reset as well.
*
* Return: %0 on success, negative errno otherwise.
*/
int dma_port_flash_update_auth(struct tb_dma_port *dma)
{
u32 in;
in = MAIL_IN_CMD_FLASH_UPDATE_AUTH << MAIL_IN_CMD_SHIFT;
in |= MAIL_IN_OP_REQUEST;
return dma_port_request(dma, in, 150);
}
/**
* dma_port_flash_update_auth_status() - Reads status of update auth command
* @dma: DMA control port
* @status: Status code of the operation
*
* The function checks if there is status available from the last update
* auth command.
*
* Return:
* * %0 - If there is no status and no further action is required.
* * %1 - If there is some status. @status holds the failure code.
* * Negative errno - An error occurred when reading status from the
* switch.
*/
int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status)
{
struct tb_switch *sw = dma->sw;
u32 out, cmd;
int ret;
ret = dma_port_read(sw->tb->ctl, &out, tb_route(sw), dma->port,
dma->base + MAIL_OUT, 1, DMA_PORT_TIMEOUT);
if (ret)
return ret;
/* Check if the status relates to flash update auth */
cmd = (out & MAIL_OUT_STATUS_CMD_MASK) >> MAIL_OUT_STATUS_CMD_SHIFT;
if (cmd == MAIL_IN_CMD_FLASH_UPDATE_AUTH) {
if (status)
*status = out & MAIL_OUT_STATUS_MASK;
/* Reset is needed in any case */
return 1;
}
return 0;
}
/**
* dma_port_power_cycle() - Power cycles the switch
* @dma: DMA control port
*
* Triggers power cycle to the switch.
*
* Return: %0 on success, negative errno otherwise.
*/
int dma_port_power_cycle(struct tb_dma_port *dma)
{
u32 in;
in = MAIL_IN_CMD_POWER_CYCLE << MAIL_IN_CMD_SHIFT;
in |= MAIL_IN_OP_REQUEST;
return dma_port_request(dma, in, 150);
}
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt DMA configuration based mailbox support
*
* Copyright (C) 2017, Intel Corporation
* Authors: Michael Jamet <michael.jamet@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#ifndef DMA_PORT_H_
#define DMA_PORT_H_
#include "tb.h"
struct tb_switch;
struct tb_dma_port;
#define DMA_PORT_CSS_ADDRESS 0x3fffff
#define DMA_PORT_CSS_MAX_SIZE SZ_128
struct tb_dma_port *dma_port_alloc(struct tb_switch *sw);
void dma_port_free(struct tb_dma_port *dma);
int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
void *buf, size_t size);
int dma_port_flash_update_auth(struct tb_dma_port *dma);
int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status);
int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
const void *buf, size_t size);
int dma_port_power_cycle(struct tb_dma_port *dma);
#endif
+763
View File
@@ -0,0 +1,763 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DMA traffic test driver
*
* Copyright (C) 2020, Intel Corporation
* Authors: Isaac Hazan <isaac.hazan@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/sizes.h>
#include <linux/thunderbolt.h>
#define DMA_TEST_TX_RING_SIZE 64
#define DMA_TEST_RX_RING_SIZE 256
#define DMA_TEST_FRAME_SIZE SZ_4K
#define DMA_TEST_DATA_PATTERN 0x0123456789abcdefLL
#define DMA_TEST_MAX_PACKETS 1000
enum dma_test_frame_pdf {
DMA_TEST_PDF_FRAME_START = 1,
DMA_TEST_PDF_FRAME_END,
};
struct dma_test_frame {
struct dma_test *dma_test;
void *data;
struct ring_frame frame;
};
enum dma_test_test_error {
DMA_TEST_NO_ERROR,
DMA_TEST_INTERRUPTED,
DMA_TEST_BUFFER_ERROR,
DMA_TEST_DMA_ERROR,
DMA_TEST_CONFIG_ERROR,
DMA_TEST_SPEED_ERROR,
DMA_TEST_WIDTH_ERROR,
DMA_TEST_BONDING_ERROR,
DMA_TEST_PACKET_ERROR,
};
static const char * const dma_test_error_names[] = {
[DMA_TEST_NO_ERROR] = "no errors",
[DMA_TEST_INTERRUPTED] = "interrupted by signal",
[DMA_TEST_BUFFER_ERROR] = "no memory for packet buffers",
[DMA_TEST_DMA_ERROR] = "DMA ring setup failed",
[DMA_TEST_CONFIG_ERROR] = "configuration is not valid",
[DMA_TEST_SPEED_ERROR] = "unexpected link speed",
[DMA_TEST_WIDTH_ERROR] = "unexpected link width",
[DMA_TEST_BONDING_ERROR] = "lane bonding configuration error",
[DMA_TEST_PACKET_ERROR] = "packet check failed",
};
enum dma_test_result {
DMA_TEST_NOT_RUN,
DMA_TEST_SUCCESS,
DMA_TEST_FAIL,
};
static const char * const dma_test_result_names[] = {
[DMA_TEST_NOT_RUN] = "not run",
[DMA_TEST_SUCCESS] = "success",
[DMA_TEST_FAIL] = "failed",
};
/**
* struct dma_test - DMA test device driver private data
* @svc: XDomain service the driver is bound to
* @xd: XDomain the service belongs to
* @rx_ring: Software ring holding RX frames
* @rx_hopid: HopID used for receiving frames
* @tx_ring: Software ring holding TX frames
* @tx_hopid: HopID used for sending fames
* @packets_to_send: Number of packets to send
* @packets_to_receive: Number of packets to receive
* @packets_sent: Actual number of packets sent
* @packets_received: Actual number of packets received
* @link_speed: Expected link speed (Gb/s), %0 to use whatever is negotiated
* @link_width: Expected link width (Gb/s), %0 to use whatever is negotiated
* @crc_errors: Number of CRC errors during the test run
* @buffer_overflow_errors: Number of buffer overflow errors during the test
* run
* @result: Result of the last run
* @error_code: Error code of the last run
* @complete: Used to wait for the Rx to complete
* @lock: Lock serializing access to this structure
*/
struct dma_test {
const struct tb_service *svc;
struct tb_xdomain *xd;
struct tb_ring *rx_ring;
int rx_hopid;
struct tb_ring *tx_ring;
int tx_hopid;
unsigned int packets_to_send;
unsigned int packets_to_receive;
unsigned int packets_sent;
unsigned int packets_received;
unsigned int link_speed;
enum tb_link_width link_width;
unsigned int crc_errors;
unsigned int buffer_overflow_errors;
enum dma_test_result result;
enum dma_test_test_error error_code;
struct completion complete;
struct mutex lock;
};
/* DMA test property directory UUID: 3188cd10-6523-4a5a-a682-fdca07a248d8 */
static const uuid_t dma_test_dir_uuid =
UUID_INIT(0x3188cd10, 0x6523, 0x4a5a,
0xa6, 0x82, 0xfd, 0xca, 0x07, 0xa2, 0x48, 0xd8);
static struct tb_property_dir *dma_test_dir;
static void *dma_test_pattern;
static void dma_test_free_rings(struct dma_test *dt)
{
if (dt->rx_ring) {
tb_xdomain_release_in_hopid(dt->xd, dt->rx_hopid);
tb_ring_free(dt->rx_ring);
dt->rx_ring = NULL;
}
if (dt->tx_ring) {
tb_xdomain_release_out_hopid(dt->xd, dt->tx_hopid);
tb_ring_free(dt->tx_ring);
dt->tx_ring = NULL;
}
}
static int dma_test_start_rings(struct dma_test *dt)
{
unsigned int flags = RING_FLAG_FRAME;
struct tb_xdomain *xd = dt->xd;
int ret, e2e_tx_hop = 0;
struct tb_ring *ring;
/*
* If we are both sender and receiver (traffic goes over a
* special loopback dongle) enable E2E flow control. This avoids
* losing packets.
*/
if (dt->packets_to_send && dt->packets_to_receive)
flags |= RING_FLAG_E2E;
if (dt->packets_to_send) {
ring = tb_ring_alloc_tx(xd->tb->nhi, -1, DMA_TEST_TX_RING_SIZE,
flags);
if (!ring)
return -ENOMEM;
dt->tx_ring = ring;
e2e_tx_hop = ring->hop;
tb_ring_throttling(ring, 128000);
ret = tb_xdomain_alloc_out_hopid(xd, -1);
if (ret < 0) {
dma_test_free_rings(dt);
return ret;
}
dt->tx_hopid = ret;
}
if (dt->packets_to_receive) {
u16 sof_mask, eof_mask;
sof_mask = BIT(DMA_TEST_PDF_FRAME_START);
eof_mask = BIT(DMA_TEST_PDF_FRAME_END);
ring = tb_ring_alloc_rx(xd->tb->nhi, -1, DMA_TEST_RX_RING_SIZE,
flags, e2e_tx_hop, sof_mask, eof_mask,
NULL, NULL);
if (!ring) {
dma_test_free_rings(dt);
return -ENOMEM;
}
dt->rx_ring = ring;
tb_ring_throttling(ring, 128000);
ret = tb_xdomain_alloc_in_hopid(xd, -1);
if (ret < 0) {
dma_test_free_rings(dt);
return ret;
}
dt->rx_hopid = ret;
}
ret = tb_xdomain_enable_paths(dt->xd, dt->tx_hopid,
dt->tx_ring ? dt->tx_ring->hop : -1,
dt->rx_hopid,
dt->rx_ring ? dt->rx_ring->hop : -1);
if (ret) {
dma_test_free_rings(dt);
return ret;
}
if (dt->tx_ring)
tb_ring_start(dt->tx_ring);
if (dt->rx_ring)
tb_ring_start(dt->rx_ring);
return 0;
}
static void dma_test_stop_rings(struct dma_test *dt)
{
int ret;
if (dt->rx_ring)
tb_ring_stop(dt->rx_ring);
if (dt->tx_ring)
tb_ring_stop(dt->tx_ring);
ret = tb_xdomain_disable_paths(dt->xd, dt->tx_hopid,
dt->tx_ring ? dt->tx_ring->hop : -1,
dt->rx_hopid,
dt->rx_ring ? dt->rx_ring->hop : -1);
if (ret)
dev_warn(&dt->svc->dev, "failed to disable DMA paths\n");
dma_test_free_rings(dt);
}
static void dma_test_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
bool canceled)
{
struct dma_test_frame *tf = container_of(frame, typeof(*tf), frame);
struct dma_test *dt = tf->dma_test;
struct device *dma_dev = tb_ring_dma_device(dt->rx_ring);
dma_unmap_single(dma_dev, tf->frame.buffer_phy, DMA_TEST_FRAME_SIZE,
DMA_FROM_DEVICE);
kfree(tf->data);
if (canceled) {
kfree(tf);
return;
}
dt->packets_received++;
dev_dbg(&dt->svc->dev, "packet %u/%u received\n", dt->packets_received,
dt->packets_to_receive);
if (tf->frame.flags & RING_DESC_CRC_ERROR)
dt->crc_errors++;
if (tf->frame.flags & RING_DESC_BUFFER_OVERRUN)
dt->buffer_overflow_errors++;
kfree(tf);
if (dt->packets_received == dt->packets_to_receive)
complete(&dt->complete);
}
static int dma_test_submit_rx(struct dma_test *dt, size_t npackets)
{
struct device *dma_dev = tb_ring_dma_device(dt->rx_ring);
int i;
for (i = 0; i < npackets; i++) {
struct dma_test_frame *tf;
dma_addr_t dma_addr;
tf = kzalloc_obj(*tf);
if (!tf)
return -ENOMEM;
tf->data = kzalloc(DMA_TEST_FRAME_SIZE, GFP_KERNEL);
if (!tf->data) {
kfree(tf);
return -ENOMEM;
}
dma_addr = dma_map_single(dma_dev, tf->data, DMA_TEST_FRAME_SIZE,
DMA_FROM_DEVICE);
if (dma_mapping_error(dma_dev, dma_addr)) {
kfree(tf->data);
kfree(tf);
return -ENOMEM;
}
tf->frame.buffer_phy = dma_addr;
tf->frame.callback = dma_test_rx_callback;
tf->dma_test = dt;
INIT_LIST_HEAD(&tf->frame.list);
tb_ring_rx(dt->rx_ring, &tf->frame);
}
return 0;
}
static void dma_test_tx_callback(struct tb_ring *ring, struct ring_frame *frame,
bool canceled)
{
struct dma_test_frame *tf = container_of(frame, typeof(*tf), frame);
struct dma_test *dt = tf->dma_test;
struct device *dma_dev = tb_ring_dma_device(dt->tx_ring);
dma_unmap_single(dma_dev, tf->frame.buffer_phy, DMA_TEST_FRAME_SIZE,
DMA_TO_DEVICE);
kfree(tf->data);
kfree(tf);
}
static int dma_test_submit_tx(struct dma_test *dt, size_t npackets)
{
struct device *dma_dev = tb_ring_dma_device(dt->tx_ring);
int i;
for (i = 0; i < npackets; i++) {
struct dma_test_frame *tf;
dma_addr_t dma_addr;
tf = kzalloc_obj(*tf);
if (!tf)
return -ENOMEM;
tf->frame.size = 0; /* means 4096 */
tf->dma_test = dt;
tf->data = kmemdup(dma_test_pattern, DMA_TEST_FRAME_SIZE, GFP_KERNEL);
if (!tf->data) {
kfree(tf);
return -ENOMEM;
}
dma_addr = dma_map_single(dma_dev, tf->data, DMA_TEST_FRAME_SIZE,
DMA_TO_DEVICE);
if (dma_mapping_error(dma_dev, dma_addr)) {
kfree(tf->data);
kfree(tf);
return -ENOMEM;
}
tf->frame.buffer_phy = dma_addr;
tf->frame.callback = dma_test_tx_callback;
tf->frame.sof = DMA_TEST_PDF_FRAME_START;
tf->frame.eof = DMA_TEST_PDF_FRAME_END;
INIT_LIST_HEAD(&tf->frame.list);
dt->packets_sent++;
dev_dbg(&dt->svc->dev, "packet %u/%u sent\n", dt->packets_sent,
dt->packets_to_send);
tb_ring_tx(dt->tx_ring, &tf->frame);
}
return 0;
}
#define DMA_TEST_DEBUGFS_ATTR(__fops, __get, __validate, __set) \
static int __fops ## _show(void *data, u64 *val) \
{ \
struct tb_service *svc = data; \
struct dma_test *dt = tb_service_get_drvdata(svc); \
int ret; \
\
ret = mutex_lock_interruptible(&dt->lock); \
if (ret) \
return ret; \
__get(dt, val); \
mutex_unlock(&dt->lock); \
return 0; \
} \
static int __fops ## _store(void *data, u64 val) \
{ \
struct tb_service *svc = data; \
struct dma_test *dt = tb_service_get_drvdata(svc); \
int ret; \
\
ret = __validate(val); \
if (ret) \
return ret; \
ret = mutex_lock_interruptible(&dt->lock); \
if (ret) \
return ret; \
__set(dt, val); \
mutex_unlock(&dt->lock); \
return 0; \
} \
DEFINE_DEBUGFS_ATTRIBUTE(__fops ## _fops, __fops ## _show, \
__fops ## _store, "%llu\n")
static void lanes_get(const struct dma_test *dt, u64 *val)
{
*val = dt->link_width;
}
static int lanes_validate(u64 val)
{
return val > 2 ? -EINVAL : 0;
}
static void lanes_set(struct dma_test *dt, u64 val)
{
dt->link_width = val;
}
DMA_TEST_DEBUGFS_ATTR(lanes, lanes_get, lanes_validate, lanes_set);
static void speed_get(const struct dma_test *dt, u64 *val)
{
*val = dt->link_speed;
}
static int speed_validate(u64 val)
{
switch (val) {
case 40:
case 20:
case 10:
case 0:
return 0;
default:
return -EINVAL;
}
}
static void speed_set(struct dma_test *dt, u64 val)
{
dt->link_speed = val;
}
DMA_TEST_DEBUGFS_ATTR(speed, speed_get, speed_validate, speed_set);
static void packets_to_receive_get(const struct dma_test *dt, u64 *val)
{
*val = dt->packets_to_receive;
}
static int packets_to_receive_validate(u64 val)
{
return val > DMA_TEST_MAX_PACKETS ? -EINVAL : 0;
}
static void packets_to_receive_set(struct dma_test *dt, u64 val)
{
dt->packets_to_receive = val;
}
DMA_TEST_DEBUGFS_ATTR(packets_to_receive, packets_to_receive_get,
packets_to_receive_validate, packets_to_receive_set);
static void packets_to_send_get(const struct dma_test *dt, u64 *val)
{
*val = dt->packets_to_send;
}
static int packets_to_send_validate(u64 val)
{
return val > DMA_TEST_MAX_PACKETS ? -EINVAL : 0;
}
static void packets_to_send_set(struct dma_test *dt, u64 val)
{
dt->packets_to_send = val;
}
DMA_TEST_DEBUGFS_ATTR(packets_to_send, packets_to_send_get,
packets_to_send_validate, packets_to_send_set);
static int dma_test_set_bonding(struct dma_test *dt)
{
switch (dt->link_width) {
case TB_LINK_WIDTH_DUAL:
return tb_xdomain_lane_bonding_enable(dt->xd);
case TB_LINK_WIDTH_SINGLE:
tb_xdomain_lane_bonding_disable(dt->xd);
fallthrough;
default:
return 0;
}
}
static bool dma_test_validate_config(struct dma_test *dt)
{
if (!dt->packets_to_send && !dt->packets_to_receive)
return false;
if (dt->packets_to_send && dt->packets_to_receive &&
dt->packets_to_send != dt->packets_to_receive)
return false;
return true;
}
static void dma_test_check_errors(struct dma_test *dt, int ret)
{
if (!dt->error_code) {
if (dt->link_speed && dt->xd->link_speed != dt->link_speed) {
dt->error_code = DMA_TEST_SPEED_ERROR;
} else if (dt->link_width && dt->link_width != dt->xd->link_width) {
dt->error_code = DMA_TEST_WIDTH_ERROR;
} else if (dt->packets_to_send != dt->packets_sent ||
dt->packets_to_receive != dt->packets_received ||
dt->crc_errors || dt->buffer_overflow_errors) {
dt->error_code = DMA_TEST_PACKET_ERROR;
} else {
return;
}
}
dt->result = DMA_TEST_FAIL;
}
static int test_store(void *data, u64 val)
{
struct tb_service *svc = data;
struct dma_test *dt = tb_service_get_drvdata(svc);
int ret;
if (val != 1)
return -EINVAL;
ret = mutex_lock_interruptible(&dt->lock);
if (ret)
return ret;
dt->packets_sent = 0;
dt->packets_received = 0;
dt->crc_errors = 0;
dt->buffer_overflow_errors = 0;
dt->result = DMA_TEST_SUCCESS;
dt->error_code = DMA_TEST_NO_ERROR;
dev_dbg(&svc->dev, "DMA test starting\n");
if (dt->link_speed)
dev_dbg(&svc->dev, "link_speed: %u Gb/s\n", dt->link_speed);
if (dt->link_width)
dev_dbg(&svc->dev, "link_width: %u\n", dt->link_width);
dev_dbg(&svc->dev, "packets_to_send: %u\n", dt->packets_to_send);
dev_dbg(&svc->dev, "packets_to_receive: %u\n", dt->packets_to_receive);
if (!dma_test_validate_config(dt)) {
dev_err(&svc->dev, "invalid test configuration\n");
dt->error_code = DMA_TEST_CONFIG_ERROR;
goto out_unlock;
}
ret = dma_test_set_bonding(dt);
if (ret) {
dev_err(&svc->dev, "failed to set lanes\n");
dt->error_code = DMA_TEST_BONDING_ERROR;
goto out_unlock;
}
ret = dma_test_start_rings(dt);
if (ret) {
dev_err(&svc->dev, "failed to enable DMA rings\n");
dt->error_code = DMA_TEST_DMA_ERROR;
goto out_unlock;
}
if (dt->packets_to_receive) {
reinit_completion(&dt->complete);
ret = dma_test_submit_rx(dt, dt->packets_to_receive);
if (ret) {
dev_err(&svc->dev, "failed to submit receive buffers\n");
dt->error_code = DMA_TEST_BUFFER_ERROR;
goto out_stop;
}
}
if (dt->packets_to_send) {
ret = dma_test_submit_tx(dt, dt->packets_to_send);
if (ret) {
dev_err(&svc->dev, "failed to submit transmit buffers\n");
dt->error_code = DMA_TEST_BUFFER_ERROR;
goto out_stop;
}
}
if (dt->packets_to_receive) {
ret = wait_for_completion_interruptible(&dt->complete);
if (ret) {
dt->error_code = DMA_TEST_INTERRUPTED;
goto out_stop;
}
}
out_stop:
dma_test_stop_rings(dt);
out_unlock:
dma_test_check_errors(dt, ret);
mutex_unlock(&dt->lock);
dev_dbg(&svc->dev, "DMA test %s\n", dma_test_result_names[dt->result]);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(test_fops, NULL, test_store, "%llu\n");
static int status_show(struct seq_file *s, void *not_used)
{
struct tb_service *svc = s->private;
struct dma_test *dt = tb_service_get_drvdata(svc);
int ret;
ret = mutex_lock_interruptible(&dt->lock);
if (ret)
return ret;
seq_printf(s, "result: %s\n", dma_test_result_names[dt->result]);
if (dt->result == DMA_TEST_NOT_RUN)
goto out_unlock;
seq_printf(s, "packets received: %u\n", dt->packets_received);
seq_printf(s, "packets sent: %u\n", dt->packets_sent);
seq_printf(s, "CRC errors: %u\n", dt->crc_errors);
seq_printf(s, "buffer overflow errors: %u\n",
dt->buffer_overflow_errors);
seq_printf(s, "error: %s\n", dma_test_error_names[dt->error_code]);
out_unlock:
mutex_unlock(&dt->lock);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(status);
static void dma_test_debugfs_init(struct tb_service *svc)
{
struct dentry *debugfs_dir;
debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
debugfs_create_file("lanes", 0600, debugfs_dir, svc, &lanes_fops);
debugfs_create_file("speed", 0600, debugfs_dir, svc, &speed_fops);
debugfs_create_file("packets_to_receive", 0600, debugfs_dir, svc,
&packets_to_receive_fops);
debugfs_create_file("packets_to_send", 0600, debugfs_dir, svc,
&packets_to_send_fops);
debugfs_create_file("status", 0400, debugfs_dir, svc, &status_fops);
debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops);
}
static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id)
{
struct tb_xdomain *xd = tb_service_parent(svc);
struct dma_test *dt;
dt = devm_kzalloc(&svc->dev, sizeof(*dt), GFP_KERNEL);
if (!dt)
return -ENOMEM;
dt->svc = svc;
dt->xd = xd;
mutex_init(&dt->lock);
init_completion(&dt->complete);
tb_service_set_drvdata(svc, dt);
dma_test_debugfs_init(svc);
return 0;
}
static void dma_test_remove(struct tb_service *svc)
{
struct dma_test *dt = tb_service_get_drvdata(svc);
mutex_lock(&dt->lock);
debugfs_lookup_and_remove("dma_test", svc->debugfs_dir);
mutex_unlock(&dt->lock);
}
static int __maybe_unused dma_test_suspend(struct device *dev)
{
/*
* No need to do anything special here. If userspace is writing
* to the test attribute when suspend started, it comes out from
* wait_for_completion_interruptible() with -ERESTARTSYS and the
* DMA test fails tearing down the rings. Once userspace is
* thawed the kernel restarts the write syscall effectively
* re-running the test.
*/
return 0;
}
static int __maybe_unused dma_test_resume(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops dma_test_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dma_test_suspend, dma_test_resume)
};
static const struct tb_service_id dma_test_ids[] = {
{ TB_SERVICE("dma_test", 1) },
{ },
};
MODULE_DEVICE_TABLE(tbsvc, dma_test_ids);
static struct tb_service_driver dma_test_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "thunderbolt_dma_test",
.pm = &dma_test_pm_ops,
},
.probe = dma_test_probe,
.remove = dma_test_remove,
.id_table = dma_test_ids,
};
static int __init dma_test_init(void)
{
u64 data_value = DMA_TEST_DATA_PATTERN;
int i, ret;
dma_test_pattern = kmalloc(DMA_TEST_FRAME_SIZE, GFP_KERNEL);
if (!dma_test_pattern)
return -ENOMEM;
for (i = 0; i < DMA_TEST_FRAME_SIZE / sizeof(data_value); i++)
((u32 *)dma_test_pattern)[i] = data_value++;
dma_test_dir = tb_property_create_dir(&dma_test_dir_uuid);
if (!dma_test_dir) {
ret = -ENOMEM;
goto err_free_pattern;
}
tb_property_add_immediate(dma_test_dir, "prtcid", 1);
tb_property_add_immediate(dma_test_dir, "prtcvers", 1);
tb_property_add_immediate(dma_test_dir, "prtcrevs", 0);
tb_property_add_immediate(dma_test_dir, "prtcstns", 0);
ret = tb_register_property_dir("dma_test", dma_test_dir);
if (ret)
goto err_free_dir;
ret = tb_register_service_driver(&dma_test_driver);
if (ret)
goto err_unregister_dir;
return 0;
err_unregister_dir:
tb_unregister_property_dir("dma_test", dma_test_dir);
err_free_dir:
tb_property_free_dir(dma_test_dir);
err_free_pattern:
kfree(dma_test_pattern);
return ret;
}
module_init(dma_test_init);
static void __exit dma_test_exit(void)
{
tb_unregister_service_driver(&dma_test_driver);
tb_unregister_property_dir("dma_test", dma_test_dir);
tb_property_free_dir(dma_test_dir);
kfree(dma_test_pattern);
}
module_exit(dma_test_exit);
MODULE_AUTHOR("Isaac Hazan <isaac.hazan@intel.com>");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
MODULE_DESCRIPTION("Thunderbolt/USB4 DMA traffic test driver");
MODULE_LICENSE("GPL v2");
+921
View File
@@ -0,0 +1,921 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt bus support
*
* Copyright (C) 2017, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <crypto/sha2.h>
#include <crypto/utils.h>
#include "tb.h"
static DEFINE_IDA(tb_domain_ida);
static bool match_service_id(const struct tb_service_id *id,
const struct tb_service *svc)
{
if (id->match_flags & TBSVC_MATCH_PROTOCOL_KEY) {
if (strcmp(id->protocol_key, svc->key))
return false;
}
if (id->match_flags & TBSVC_MATCH_PROTOCOL_ID) {
if (id->protocol_id != svc->prtcid)
return false;
}
if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
if (id->protocol_version != svc->prtcvers)
return false;
}
if (id->match_flags & TBSVC_MATCH_PROTOCOL_REVISION) {
if (id->protocol_revision != svc->prtcrevs)
return false;
}
return true;
}
static const struct tb_service_id *__tb_service_match(struct device *dev,
const struct device_driver *drv)
{
const struct tb_service_driver *driver;
const struct tb_service_id *ids;
struct tb_service *svc;
svc = tb_to_service(dev);
if (!svc)
return NULL;
driver = container_of_const(drv, struct tb_service_driver, driver);
if (!driver->id_table)
return NULL;
for (ids = driver->id_table; ids->match_flags != 0; ids++) {
if (match_service_id(ids, svc))
return ids;
}
return NULL;
}
static int tb_service_match(struct device *dev, const struct device_driver *drv)
{
return !!__tb_service_match(dev, drv);
}
static int tb_service_probe(struct device *dev)
{
struct tb_service *svc = tb_to_service(dev);
struct tb_service_driver *driver;
const struct tb_service_id *id;
driver = container_of(dev->driver, struct tb_service_driver, driver);
id = __tb_service_match(dev, &driver->driver);
return driver->probe(svc, id);
}
static void tb_service_remove(struct device *dev)
{
struct tb_service *svc = tb_to_service(dev);
struct tb_service_driver *driver;
driver = container_of(dev->driver, struct tb_service_driver, driver);
if (driver->remove)
driver->remove(svc);
}
static void tb_service_shutdown(struct device *dev)
{
struct tb_service_driver *driver;
struct tb_service *svc;
svc = tb_to_service(dev);
if (!svc || !dev->driver)
return;
driver = container_of(dev->driver, struct tb_service_driver, driver);
if (driver->shutdown)
driver->shutdown(svc);
}
static const char * const tb_security_names[] = {
[TB_SECURITY_NONE] = "none",
[TB_SECURITY_USER] = "user",
[TB_SECURITY_SECURE] = "secure",
[TB_SECURITY_DPONLY] = "dponly",
[TB_SECURITY_USBONLY] = "usbonly",
[TB_SECURITY_NOPCIE] = "nopcie",
};
static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct tb *tb = container_of(dev, struct tb, dev);
uuid_t *uuids;
ssize_t ret;
int i;
uuids = kzalloc_objs(uuid_t, tb->nboot_acl);
if (!uuids)
return -ENOMEM;
pm_runtime_get_sync(&tb->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out;
}
ret = tb->cm_ops->get_boot_acl(tb, uuids, tb->nboot_acl);
if (ret) {
mutex_unlock(&tb->lock);
goto out;
}
mutex_unlock(&tb->lock);
for (ret = 0, i = 0; i < tb->nboot_acl; i++) {
if (!uuid_is_null(&uuids[i]))
ret += sysfs_emit_at(buf, ret, "%pUb", &uuids[i]);
ret += sysfs_emit_at(buf, ret, "%s", i < tb->nboot_acl - 1 ? "," : "\n");
}
out:
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
kfree(uuids);
return ret;
}
static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct tb *tb = container_of(dev, struct tb, dev);
char *str, *s, *uuid_str;
ssize_t ret = 0;
uuid_t *acl;
int i = 0;
/*
* Make sure the value is not bigger than tb->nboot_acl * UUID
* length + commas and optional "\n". Also the smallest allowable
* string is tb->nboot_acl * ",".
*/
if (count > (UUID_STRING_LEN + 1) * tb->nboot_acl + 1)
return -EINVAL;
if (count < tb->nboot_acl - 1)
return -EINVAL;
str = kstrdup(buf, GFP_KERNEL);
if (!str)
return -ENOMEM;
acl = kzalloc_objs(uuid_t, tb->nboot_acl);
if (!acl) {
ret = -ENOMEM;
goto err_free_str;
}
uuid_str = strim(str);
while ((s = strsep(&uuid_str, ",")) != NULL && i < tb->nboot_acl) {
size_t len = strlen(s);
if (len) {
if (len != UUID_STRING_LEN) {
ret = -EINVAL;
goto err_free_acl;
}
ret = uuid_parse(s, &acl[i]);
if (ret)
goto err_free_acl;
}
i++;
}
if (s || i < tb->nboot_acl) {
ret = -EINVAL;
goto err_free_acl;
}
pm_runtime_get_sync(&tb->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto err_rpm_put;
}
ret = tb->cm_ops->set_boot_acl(tb, acl, tb->nboot_acl);
if (!ret) {
/* Notify userspace about the change */
tb_domain_event(tb, NULL);
}
mutex_unlock(&tb->lock);
err_rpm_put:
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
err_free_acl:
kfree(acl);
err_free_str:
kfree(str);
return ret ?: count;
}
static DEVICE_ATTR_RW(boot_acl);
static ssize_t deauthorization_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
const struct tb *tb = container_of(dev, struct tb, dev);
bool deauthorization = false;
/* Only meaningful if authorization is supported */
if (tb->security_level == TB_SECURITY_USER ||
tb->security_level == TB_SECURITY_SECURE)
deauthorization = !!tb->cm_ops->disapprove_switch;
return sysfs_emit(buf, "%d\n", deauthorization);
}
static DEVICE_ATTR_RO(deauthorization);
static ssize_t iommu_dma_protection_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tb *tb = container_of(dev, struct tb, dev);
return sysfs_emit(buf, "%d\n", tb->nhi->iommu_dma_protection);
}
static DEVICE_ATTR_RO(iommu_dma_protection);
static ssize_t security_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct tb *tb = container_of(dev, struct tb, dev);
const char *name = "unknown";
if (tb->security_level < ARRAY_SIZE(tb_security_names))
name = tb_security_names[tb->security_level];
return sysfs_emit(buf, "%s\n", name);
}
static DEVICE_ATTR_RO(security);
static struct attribute *domain_attrs[] = {
&dev_attr_boot_acl.attr,
&dev_attr_deauthorization.attr,
&dev_attr_iommu_dma_protection.attr,
&dev_attr_security.attr,
NULL,
};
static umode_t domain_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct tb *tb = container_of(dev, struct tb, dev);
if (attr == &dev_attr_boot_acl.attr) {
if (tb->nboot_acl &&
tb->cm_ops->get_boot_acl &&
tb->cm_ops->set_boot_acl)
return attr->mode;
return 0;
}
return attr->mode;
}
static const struct attribute_group domain_attr_group = {
.is_visible = domain_attr_is_visible,
.attrs = domain_attrs,
};
static const struct attribute_group *domain_attr_groups[] = {
&domain_attr_group,
NULL,
};
const struct bus_type tb_bus_type = {
.name = "thunderbolt",
.match = tb_service_match,
.probe = tb_service_probe,
.remove = tb_service_remove,
.shutdown = tb_service_shutdown,
};
static void tb_domain_release(struct device *dev)
{
struct tb *tb = container_of(dev, struct tb, dev);
struct tb_nhi *nhi = tb->nhi;
tb_ctl_free(tb->ctl);
destroy_workqueue(tb->wq);
ida_free(&tb_domain_ida, tb->index);
mutex_destroy(&tb->lock);
kfree(tb);
complete(&nhi->domain_released);
}
const struct device_type tb_domain_type = {
.name = "thunderbolt_domain",
.release = tb_domain_release,
};
static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size)
{
struct tb *tb = data;
if (!tb->cm_ops->handle_event) {
tb_warn(tb, "domain does not have event handler\n");
return true;
}
switch (type) {
case TB_CFG_PKG_XDOMAIN_REQ:
case TB_CFG_PKG_XDOMAIN_RESP:
if (tb_is_xdomain_enabled())
return tb_xdomain_handle_request(tb, type, buf, size);
break;
default:
tb->cm_ops->handle_event(tb, type, buf, size);
}
return true;
}
/**
* tb_domain_alloc() - Allocate a domain
* @nhi: Pointer to the host controller
* @timeout_msec: Control channel timeout for non-raw messages
* @privsize: Size of the connection manager private data
*
* Allocates and initializes a new Thunderbolt domain. Connection
* managers are expected to call this and then fill in @cm_ops
* accordingly.
*
* Call tb_domain_put() to release the domain before it has been added
* to the system.
*
* Return: Pointer to &struct tb or %NULL in case of error.
*/
struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize)
{
struct tb *tb;
/*
* Make sure the structure sizes map with what the hardware
* expects because bit-fields are being used.
*/
BUILD_BUG_ON(sizeof(struct tb_regs_switch_header) != 5 * 4);
BUILD_BUG_ON(sizeof(struct tb_regs_port_header) != 8 * 4);
BUILD_BUG_ON(sizeof(struct tb_regs_hop) != 2 * 4);
tb = kzalloc(sizeof(*tb) + privsize, GFP_KERNEL);
if (!tb)
return NULL;
tb->nhi = nhi;
mutex_init(&tb->lock);
tb->index = ida_alloc(&tb_domain_ida, GFP_KERNEL);
if (tb->index < 0)
goto err_free;
tb->wq = alloc_ordered_workqueue("thunderbolt%d", 0, tb->index);
if (!tb->wq)
goto err_remove_ida;
tb->ctl = tb_ctl_alloc(nhi, tb->index, timeout_msec, tb_domain_event_cb, tb);
if (!tb->ctl)
goto err_destroy_wq;
tb->dev.parent = nhi->dev;
tb->dev.bus = &tb_bus_type;
tb->dev.type = &tb_domain_type;
tb->dev.groups = domain_attr_groups;
dev_set_name(&tb->dev, "domain%d", tb->index);
device_initialize(&tb->dev);
return tb;
err_destroy_wq:
destroy_workqueue(tb->wq);
err_remove_ida:
ida_free(&tb_domain_ida, tb->index);
err_free:
kfree(tb);
return NULL;
}
/**
* tb_domain_add() - Add domain to the system
* @tb: Domain to add
* @reset: Issue reset to the host router
*
* Starts the domain and adds it to the system. Hotplugging devices will
* work after this has been returned successfully. In order to remove
* and release the domain after this function has been called, call
* tb_domain_remove().
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_domain_add(struct tb *tb, bool reset)
{
int ret;
if (WARN_ON(!tb->cm_ops))
return -EINVAL;
mutex_lock(&tb->lock);
/*
* tb_schedule_hotplug_handler may be called as soon as the config
* channel is started. Thats why we have to hold the lock here.
*/
tb_ctl_start(tb->ctl);
if (tb->cm_ops->driver_ready) {
ret = tb->cm_ops->driver_ready(tb);
if (ret)
goto err_ctl_stop;
}
tb_dbg(tb, "security level set to %s\n",
tb_security_names[tb->security_level]);
ret = device_add(&tb->dev);
if (ret)
goto err_ctl_stop;
/* Start the domain */
if (tb->cm_ops->start) {
ret = tb->cm_ops->start(tb, reset);
if (ret)
goto err_domain_del;
}
/* This starts event processing */
mutex_unlock(&tb->lock);
device_init_wakeup(&tb->dev, true);
pm_runtime_no_callbacks(&tb->dev);
pm_runtime_set_active(&tb->dev);
pm_runtime_enable(&tb->dev);
pm_runtime_set_autosuspend_delay(&tb->dev, TB_AUTOSUSPEND_DELAY);
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_use_autosuspend(&tb->dev);
return 0;
err_domain_del:
device_del(&tb->dev);
err_ctl_stop:
tb_ctl_stop(tb->ctl);
mutex_unlock(&tb->lock);
return ret;
}
/**
* tb_domain_remove() - Removes and releases a domain
* @tb: Domain to remove
*
* Stops the domain, removes it from the system and releases all
* resources once the last reference has been released.
*/
void tb_domain_remove(struct tb *tb)
{
mutex_lock(&tb->lock);
if (tb->cm_ops->stop)
tb->cm_ops->stop(tb);
/* Stop the domain control traffic */
tb_ctl_stop(tb->ctl);
mutex_unlock(&tb->lock);
flush_workqueue(tb->wq);
if (tb->cm_ops->deinit)
tb->cm_ops->deinit(tb);
device_unregister(&tb->dev);
}
/**
* tb_domain_suspend_noirq() - Suspend a domain
* @tb: Domain to suspend
*
* Suspends all devices in the domain and stops the control channel.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_domain_suspend_noirq(struct tb *tb)
{
int ret = 0;
/*
* The control channel interrupt is left enabled during suspend
* and taking the lock here prevents any events happening before
* we actually have stopped the domain and the control channel.
*/
mutex_lock(&tb->lock);
if (tb->cm_ops->suspend_noirq)
ret = tb->cm_ops->suspend_noirq(tb);
if (!ret)
tb_ctl_stop(tb->ctl);
mutex_unlock(&tb->lock);
return ret;
}
/**
* tb_domain_resume_noirq() - Resume a domain
* @tb: Domain to resume
*
* Re-starts the control channel, and resumes all devices connected to
* the domain.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_domain_resume_noirq(struct tb *tb)
{
int ret = 0;
mutex_lock(&tb->lock);
tb_ctl_start(tb->ctl);
if (tb->cm_ops->resume_noirq)
ret = tb->cm_ops->resume_noirq(tb);
mutex_unlock(&tb->lock);
return ret;
}
int tb_domain_suspend(struct tb *tb)
{
return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0;
}
int tb_domain_freeze_noirq(struct tb *tb)
{
int ret = 0;
mutex_lock(&tb->lock);
if (tb->cm_ops->freeze_noirq)
ret = tb->cm_ops->freeze_noirq(tb);
if (!ret)
tb_ctl_stop(tb->ctl);
mutex_unlock(&tb->lock);
return ret;
}
int tb_domain_thaw_noirq(struct tb *tb)
{
int ret = 0;
mutex_lock(&tb->lock);
tb_ctl_start(tb->ctl);
if (tb->cm_ops->thaw_noirq)
ret = tb->cm_ops->thaw_noirq(tb);
mutex_unlock(&tb->lock);
return ret;
}
void tb_domain_complete(struct tb *tb)
{
if (tb->cm_ops->complete)
tb->cm_ops->complete(tb);
}
int tb_domain_runtime_suspend(struct tb *tb)
{
if (tb->cm_ops->runtime_suspend) {
int ret = tb->cm_ops->runtime_suspend(tb);
if (ret)
return ret;
}
tb_ctl_stop(tb->ctl);
return 0;
}
int tb_domain_runtime_resume(struct tb *tb)
{
tb_ctl_start(tb->ctl);
if (tb->cm_ops->runtime_resume) {
int ret = tb->cm_ops->runtime_resume(tb);
if (ret)
return ret;
}
return 0;
}
/**
* tb_domain_disapprove_switch() - Disapprove switch
* @tb: Domain the switch belongs to
* @sw: Switch to disapprove
*
* This will disconnect PCIe tunnel from parent to this @sw.
*
* Return: %0 on success and negative errno in case of failure.
*/
int tb_domain_disapprove_switch(struct tb *tb, struct tb_switch *sw)
{
if (!tb->cm_ops->disapprove_switch)
return -EPERM;
return tb->cm_ops->disapprove_switch(tb, sw);
}
/**
* tb_domain_approve_switch() - Approve switch
* @tb: Domain the switch belongs to
* @sw: Switch to approve
*
* This will approve switch by connection manager specific means. In
* case of success the connection manager will create PCIe tunnel from
* parent to @sw.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw)
{
struct tb_switch *parent_sw;
if (!tb->cm_ops->approve_switch)
return -EPERM;
/* The parent switch must be authorized before this one */
parent_sw = tb_to_switch(sw->dev.parent);
if (!parent_sw || !parent_sw->authorized)
return -EINVAL;
return tb->cm_ops->approve_switch(tb, sw);
}
/**
* tb_domain_approve_switch_key() - Approve switch and add key
* @tb: Domain the switch belongs to
* @sw: Switch to approve
*
* For switches that support secure connect, this function first adds
* key to the switch NVM using connection manager specific means. If
* adding the key is successful, the switch is approved and connected.
*
* Return: %0 on success and negative errno in case of failure.
*/
int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw)
{
struct tb_switch *parent_sw;
int ret;
if (!tb->cm_ops->approve_switch || !tb->cm_ops->add_switch_key)
return -EPERM;
/* The parent switch must be authorized before this one */
parent_sw = tb_to_switch(sw->dev.parent);
if (!parent_sw || !parent_sw->authorized)
return -EINVAL;
ret = tb->cm_ops->add_switch_key(tb, sw);
if (ret)
return ret;
return tb->cm_ops->approve_switch(tb, sw);
}
/**
* tb_domain_challenge_switch_key() - Challenge and approve switch
* @tb: Domain the switch belongs to
* @sw: Switch to approve
*
* For switches that support secure connect, this function generates
* random challenge and sends it to the switch. The switch responds to
* this and if the response matches our random challenge, the switch is
* approved and connected.
*
* Return: %0 on success and negative errno in case of failure.
*/
int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw)
{
u8 challenge[TB_SWITCH_KEY_SIZE];
u8 response[TB_SWITCH_KEY_SIZE];
u8 hmac[TB_SWITCH_KEY_SIZE];
struct tb_switch *parent_sw;
int ret;
if (!tb->cm_ops->approve_switch || !tb->cm_ops->challenge_switch_key)
return -EPERM;
/* The parent switch must be authorized before this one */
parent_sw = tb_to_switch(sw->dev.parent);
if (!parent_sw || !parent_sw->authorized)
return -EINVAL;
get_random_bytes(challenge, sizeof(challenge));
ret = tb->cm_ops->challenge_switch_key(tb, sw, challenge, response);
if (ret)
return ret;
static_assert(sizeof(hmac) == SHA256_DIGEST_SIZE);
hmac_sha256_usingrawkey(sw->key, TB_SWITCH_KEY_SIZE,
challenge, sizeof(challenge), hmac);
/* The returned HMAC must match the one we calculated */
if (crypto_memneq(response, hmac, sizeof(hmac)))
return -EKEYREJECTED;
return tb->cm_ops->approve_switch(tb, sw);
}
/**
* tb_domain_disconnect_pcie_paths() - Disconnect all PCIe paths
* @tb: Domain whose PCIe paths to disconnect
*
* This needs to be called in preparation for NVM upgrade of the host
* controller. Makes sure all PCIe paths are disconnected.
*
* Return: %0 on success and negative errno in case of error.
*/
int tb_domain_disconnect_pcie_paths(struct tb *tb)
{
if (!tb->cm_ops->disconnect_pcie_paths)
return -EPERM;
return tb->cm_ops->disconnect_pcie_paths(tb);
}
/**
* tb_domain_approve_xdomain_paths() - Enable DMA paths for XDomain
* @tb: Domain enabling the DMA paths
* @xd: XDomain DMA paths are created to
* @transmit_path: HopID we are using to send out packets
* @transmit_ring: DMA ring used to send out packets
* @receive_path: HopID the other end is using to send packets to us
* @receive_ring: DMA ring used to receive packets from @receive_path
*
* Calls connection manager specific method to enable DMA paths to the
* XDomain in question.
*
* Return:
* * %0 - On success.
* * %-ENOTSUPP - If the connection manager implementation does not support
* XDomains.
* * Negative errno - An error occurred.
*/
int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring)
{
if (!tb->cm_ops->approve_xdomain_paths)
return -ENOTSUPP;
return tb->cm_ops->approve_xdomain_paths(tb, xd, transmit_path,
transmit_ring, receive_path, receive_ring);
}
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
* @xd: XDomain whose DMA paths are disconnected
* @transmit_path: HopID we are using to send out packets
* @transmit_ring: DMA ring used to send out packets
* @receive_path: HopID the other end is using to send packets to us
* @receive_ring: DMA ring used to receive packets from @receive_path
*
* Calls connection manager specific method to disconnect DMA paths to
* the XDomain in question.
*
* Return:
* * %0 - On success.
* * %-ENOTSUPP - If the connection manager implementation does not support
* XDomains.
* * Negative errno - An error occurred.
*/
int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
int transmit_path, int transmit_ring,
int receive_path, int receive_ring)
{
if (!tb->cm_ops->disconnect_xdomain_paths)
return -ENOTSUPP;
return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
transmit_ring, receive_path, receive_ring);
}
static int disconnect_xdomain(struct device *dev, void *data)
{
struct tb_xdomain *xd;
struct tb *tb = data;
int ret = 0;
xd = tb_to_xdomain(dev);
if (xd && xd->tb == tb)
ret = tb_xdomain_disable_all_paths(xd);
return ret;
}
/**
* tb_domain_disconnect_all_paths() - Disconnect all paths for the domain
* @tb: Domain whose paths are disconnected
*
* This function can be used to disconnect all paths (PCIe, XDomain) for
* example in preparation for host NVM firmware upgrade. After this is
* called the paths cannot be established without resetting the switch.
*
* Return: %0 in case of success and negative errno otherwise.
*/
int tb_domain_disconnect_all_paths(struct tb *tb)
{
int ret;
ret = tb_domain_disconnect_pcie_paths(tb);
if (ret)
return ret;
return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
}
struct unregister_context {
const struct tb *tb;
int n;
};
static int unregister_unplugged_xdomain(struct device *dev, void *data)
{
struct unregister_context *ctx = data;
struct tb_xdomain *xd;
xd = tb_to_xdomain(dev);
if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
tb_xdomain_unregister(xd);
ctx->n++;
}
return 0;
}
int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
{
struct unregister_context ctx;
ctx.tb = tb_domain_get(tb);
ctx.n = 0;
bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
tb_domain_put(tb);
return ctx.n;
}
int tb_domain_init(void)
{
int ret;
tb_configfs_init();
tb_debugfs_init();
tb_acpi_init();
ret = tb_xdomain_init();
if (ret)
goto err_acpi;
ret = bus_register(&tb_bus_type);
if (ret)
goto err_xdomain;
return 0;
err_xdomain:
tb_xdomain_exit();
err_acpi:
tb_acpi_exit();
tb_debugfs_exit();
return ret;
}
void tb_domain_exit(void)
{
bus_unregister(&tb_bus_type);
ida_destroy(&tb_domain_ida);
tb_nvm_exit();
tb_xdomain_exit();
tb_acpi_exit();
tb_debugfs_exit();
tb_configfs_exit();
}
+731
View File
@@ -0,0 +1,731 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt driver - eeprom access
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/property.h>
#include <linux/slab.h>
#include "tb.h"
/*
* tb_eeprom_ctl_write() - write control word
*/
static int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl)
{
return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + ROUTER_CS_4, 1);
}
/*
* tb_eeprom_ctl_read() - read control word
*/
static int tb_eeprom_ctl_read(struct tb_switch *sw, struct tb_eeprom_ctl *ctl)
{
return tb_sw_read(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + ROUTER_CS_4, 1);
}
enum tb_eeprom_transfer {
TB_EEPROM_IN,
TB_EEPROM_OUT,
};
/*
* tb_eeprom_active - enable rom access
*
* WARNING: Always disable access after usage. Otherwise the controller will
* fail to reprobe.
*/
static int tb_eeprom_active(struct tb_switch *sw, bool enable)
{
struct tb_eeprom_ctl ctl;
int res = tb_eeprom_ctl_read(sw, &ctl);
if (res)
return res;
if (enable) {
ctl.bit_banging_enable = 1;
res = tb_eeprom_ctl_write(sw, &ctl);
if (res)
return res;
ctl.fl_cs = 0;
return tb_eeprom_ctl_write(sw, &ctl);
} else {
ctl.fl_cs = 1;
res = tb_eeprom_ctl_write(sw, &ctl);
if (res)
return res;
ctl.bit_banging_enable = 0;
return tb_eeprom_ctl_write(sw, &ctl);
}
}
/*
* tb_eeprom_transfer - transfer one bit
*
* If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->fl_do.
* If TB_EEPROM_OUT is passed, then ctl->fl_di will be written.
*/
static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl,
enum tb_eeprom_transfer direction)
{
int res;
if (direction == TB_EEPROM_OUT) {
res = tb_eeprom_ctl_write(sw, ctl);
if (res)
return res;
}
ctl->fl_sk = 1;
res = tb_eeprom_ctl_write(sw, ctl);
if (res)
return res;
if (direction == TB_EEPROM_IN) {
res = tb_eeprom_ctl_read(sw, ctl);
if (res)
return res;
}
ctl->fl_sk = 0;
return tb_eeprom_ctl_write(sw, ctl);
}
/*
* tb_eeprom_out - write one byte to the bus
*/
static int tb_eeprom_out(struct tb_switch *sw, u8 val)
{
struct tb_eeprom_ctl ctl;
int i;
int res = tb_eeprom_ctl_read(sw, &ctl);
if (res)
return res;
for (i = 0; i < 8; i++) {
ctl.fl_di = val & 0x80;
res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_OUT);
if (res)
return res;
val <<= 1;
}
return 0;
}
/*
* tb_eeprom_in - read one byte from the bus
*/
static int tb_eeprom_in(struct tb_switch *sw, u8 *val)
{
struct tb_eeprom_ctl ctl;
int i;
int res = tb_eeprom_ctl_read(sw, &ctl);
if (res)
return res;
*val = 0;
for (i = 0; i < 8; i++) {
*val <<= 1;
res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_IN);
if (res)
return res;
*val |= ctl.fl_do;
}
return 0;
}
/*
* tb_eeprom_get_drom_offset - get drom offset within eeprom
*/
static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
{
struct tb_cap_plug_events cap;
int res;
if (!sw->cap_plug_events) {
tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n");
return -ENODEV;
}
res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events,
sizeof(cap) / 4);
if (res)
return res;
if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) {
tb_sw_warn(sw, "no NVM\n");
return -ENODEV;
}
if (cap.drom_offset > 0xffff) {
tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n",
cap.drom_offset);
return -ENXIO;
}
*offset = cap.drom_offset;
return 0;
}
/*
* tb_eeprom_read_n - read count bytes from offset into val
*/
static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
size_t count)
{
u16 drom_offset;
int i, res;
res = tb_eeprom_get_drom_offset(sw, &drom_offset);
if (res)
return res;
offset += drom_offset;
res = tb_eeprom_active(sw, true);
if (res)
return res;
res = tb_eeprom_out(sw, 3);
if (res)
return res;
res = tb_eeprom_out(sw, offset >> 8);
if (res)
return res;
res = tb_eeprom_out(sw, offset);
if (res)
return res;
for (i = 0; i < count; i++) {
res = tb_eeprom_in(sw, val + i);
if (res)
return res;
}
return tb_eeprom_active(sw, false);
}
static u8 tb_crc8(u8 *data, int len)
{
int i, j;
u8 val = 0xff;
for (i = 0; i < len; i++) {
val ^= data[i];
for (j = 0; j < 8; j++)
val = (val << 1) ^ ((val & 0x80) ? 7 : 0);
}
return val;
}
static u32 tb_crc32(void *data, size_t len)
{
return ~crc32c(~0, data, len);
}
#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 */
/* BYTES 1-8 */
u64 uid;
/* BYTES 9-12 */
u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */
/* BYTE 13 */
u8 device_rom_revision; /* should be <= 1 */
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;
u8 eeprom_rev;
} __packed;
enum tb_drom_entry_type {
/* force unsigned to prevent "one-bit signed bitfield" warning */
TB_DROM_ENTRY_GENERIC = 0U,
TB_DROM_ENTRY_PORT,
};
struct tb_drom_entry_header {
u8 len;
u8 index:6;
bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */
enum tb_drom_entry_type type:1;
} __packed;
struct tb_drom_entry_generic {
struct tb_drom_entry_header header;
u8 data[];
} __packed;
struct tb_drom_entry_port {
/* BYTES 0-1 */
struct tb_drom_entry_header header;
/* BYTE 2 */
u8 dual_link_port_rid:4;
u8 link_nr:1;
u8 unknown1:2;
bool has_dual_link_port:1;
/* BYTE 3 */
u8 dual_link_port_nr:6;
u8 unknown2:2;
/* BYTES 4 - 5 TODO decode */
u8 micro2:4;
u8 micro1:4;
u8 micro3;
/* BYTES 6-7, TODO: verify (find hardware that has these set) */
u8 peer_port_rid:4;
u8 unknown3:3;
bool has_peer_port:1;
u8 peer_port_nr:6;
u8 unknown4:2;
} __packed;
/* USB4 product descriptor */
struct tb_drom_entry_desc {
struct tb_drom_entry_header header;
u16 bcdUSBSpec;
u16 idVendor;
u16 idProduct;
u16 bcdProductFWRevision;
u32 TID;
u8 productHWRevision;
};
/**
* tb_drom_read_uid_only() - Read UID directly from DROM
* @sw: Router whose UID to read
* @uid: UID is placed here
*
* Does not use the cached copy in sw->drom. Used during resume to check switch
* identity.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
{
u8 data[9];
u8 crc;
int res;
/* read uid */
res = tb_eeprom_read_n(sw, 0, data, 9);
if (res)
return res;
crc = tb_crc8(data + 1, 8);
if (crc != data[0]) {
tb_sw_warn(sw, "uid crc8 mismatch (expected: %#x, got: %#x)\n",
data[0], crc);
return -EIO;
}
*uid = *(u64 *)(data+1);
return 0;
}
static int tb_drom_parse_entry_generic(struct tb_switch *sw,
struct tb_drom_entry_header *header)
{
const struct tb_drom_entry_generic *entry =
(const struct tb_drom_entry_generic *)header;
switch (header->index) {
case 1:
/* Length includes 2 bytes header so remove it before copy */
sw->vendor_name = kstrndup(entry->data,
header->len - sizeof(*header), GFP_KERNEL);
if (!sw->vendor_name)
return -ENOMEM;
break;
case 2:
sw->device_name = kstrndup(entry->data,
header->len - sizeof(*header), GFP_KERNEL);
if (!sw->device_name)
return -ENOMEM;
break;
case 9: {
const struct tb_drom_entry_desc *desc =
(const struct tb_drom_entry_desc *)entry;
if (!sw->vendor && !sw->device) {
sw->vendor = desc->idVendor;
sw->device = desc->idProduct;
}
break;
}
}
return 0;
}
static int tb_drom_parse_entry_port(struct tb_switch *sw,
struct tb_drom_entry_header *header)
{
struct tb_port *port;
int res;
enum tb_port_type type;
/*
* Some DROMs list more ports than the controller actually has
* so we skip those but allow the parser to continue.
*/
if (header->index > sw->config.max_port_number) {
dev_info_once(&sw->dev, "ignoring unnecessary extra entries in DROM\n");
return 0;
}
port = &sw->ports[header->index];
port->disabled = header->port_disabled;
if (port->disabled)
return 0;
res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1);
if (res)
return res;
type &= 0xffffff;
if (type == TB_TYPE_PORT) {
struct tb_drom_entry_port *entry = (void *) header;
if (header->len != sizeof(*entry)) {
tb_sw_warn(sw,
"port entry has size %#x (expected %#zx)\n",
header->len, sizeof(struct tb_drom_entry_port));
return -EIO;
}
port->link_nr = entry->link_nr;
if (entry->has_dual_link_port) {
if (entry->dual_link_port_nr > sw->config.max_port_number) {
tb_sw_warn(sw,
"port entry has invalid dual link port number %u\n",
entry->dual_link_port_nr);
return -EIO;
}
port->dual_link_port =
&port->sw->ports[entry->dual_link_port_nr];
}
}
return 0;
}
/*
* tb_drom_parse_entries - parse the linked list of drom entries
*
* Drom must have been copied to sw->drom.
*/
static int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size)
{
struct tb_drom_header *header = (void *) sw->drom;
u16 pos = header_size;
u16 drom_size = header->data_len + TB_DROM_DATA_START;
int res;
while (pos < drom_size) {
struct tb_drom_entry_header *entry = (void *) (sw->drom + pos);
if (pos + 1 == drom_size || pos + entry->len > drom_size
|| !entry->len) {
tb_sw_warn(sw, "DROM buffer overrun\n");
return -EIO;
}
switch (entry->type) {
case TB_DROM_ENTRY_GENERIC:
res = tb_drom_parse_entry_generic(sw, entry);
break;
case TB_DROM_ENTRY_PORT:
res = tb_drom_parse_entry_port(sw, entry);
break;
}
if (res)
return res;
pos += entry->len;
}
return 0;
}
static int tb_switch_drom_alloc(struct tb_switch *sw, size_t size)
{
sw->drom = kzalloc(size, GFP_KERNEL);
if (!sw->drom)
return -ENOMEM;
#ifdef CONFIG_DEBUG_FS
sw->drom_blob.data = sw->drom;
sw->drom_blob.size = size;
#endif
return 0;
}
static void tb_switch_drom_free(struct tb_switch *sw)
{
#ifdef CONFIG_DEBUG_FS
sw->drom_blob.data = NULL;
sw->drom_blob.size = 0;
#endif
kfree(sw->drom);
sw->drom = NULL;
}
/*
* tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present
*/
static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
{
struct device *dev = sw->tb->nhi->dev;
int len, res;
len = device_property_count_u8(dev, "ThunderboltDROM");
if (len < 0 || len < sizeof(struct tb_drom_header))
return -EINVAL;
res = tb_switch_drom_alloc(sw, len);
if (res)
return res;
res = device_property_read_u8_array(dev, "ThunderboltDROM", sw->drom,
len);
if (res)
goto err;
*size = ((struct tb_drom_header *)sw->drom)->data_len +
TB_DROM_DATA_START;
if (*size > len)
goto err;
return 0;
err:
tb_switch_drom_free(sw);
return -EINVAL;
}
static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
{
u16 drom_offset;
int ret;
if (!sw->dma_port)
return -ENODEV;
ret = tb_eeprom_get_drom_offset(sw, &drom_offset);
if (ret)
return ret;
if (!drom_offset)
return -ENODEV;
ret = dma_port_flash_read(sw->dma_port, drom_offset + 14, size,
sizeof(*size));
if (ret)
return ret;
/* Size includes CRC8 + UID + CRC32 */
*size += 1 + 8 + 4;
ret = tb_switch_drom_alloc(sw, *size);
if (ret)
return ret;
ret = dma_port_flash_read(sw->dma_port, drom_offset, sw->drom, *size);
if (ret) {
tb_switch_drom_free(sw);
return ret;
}
/*
* Read UID from the minimal DROM because the one in NVM is just
* a placeholder.
*/
tb_drom_read_uid_only(sw, &sw->uid);
return 0;
}
static int usb4_copy_drom(struct tb_switch *sw, u16 *size)
{
int ret;
ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size));
if (ret)
return ret;
/* Size includes CRC8 + UID + CRC32 */
*size += 1 + 8 + 4;
ret = tb_switch_drom_alloc(sw, *size);
if (ret)
return ret;
ret = usb4_switch_drom_read(sw, 0, sw->drom, *size);
if (ret)
tb_switch_drom_free(sw);
return ret;
}
static int tb_drom_bit_bang(struct tb_switch *sw, u16 *size)
{
int ret;
ret = tb_eeprom_read_n(sw, 14, (u8 *)size, 2);
if (ret)
return ret;
*size &= 0x3ff;
*size += TB_DROM_DATA_START;
tb_sw_dbg(sw, "reading DROM (length: %#x)\n", *size);
if (*size < sizeof(struct tb_drom_header)) {
tb_sw_warn(sw, "DROM too small, aborting\n");
return -EIO;
}
ret = tb_switch_drom_alloc(sw, *size);
if (ret)
return ret;
ret = tb_eeprom_read_n(sw, 0, sw->drom, *size);
if (ret)
tb_switch_drom_free(sw);
return ret;
}
static int tb_drom_parse_v1(struct tb_switch *sw)
{
const struct tb_drom_header *header =
(const struct tb_drom_header *)sw->drom;
u32 crc;
crc = tb_crc8((u8 *) &header->uid, 8);
if (crc != header->uid_crc8) {
tb_sw_warn(sw,
"DROM UID CRC8 mismatch (expected: %#x, got: %#x)\n",
header->uid_crc8, crc);
return -EIO;
}
if (!sw->uid)
sw->uid = header->uid;
sw->vendor = header->vendor_id;
sw->device = header->model_id;
crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
if (crc != header->data_crc32) {
tb_sw_warn(sw,
"DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n",
header->data_crc32, crc);
}
return tb_drom_parse_entries(sw, TB_DROM_HEADER_SIZE);
}
static int usb4_drom_parse(struct tb_switch *sw)
{
const struct tb_drom_header *header =
(const struct tb_drom_header *)sw->drom;
u32 crc;
crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
if (crc != header->data_crc32) {
tb_sw_warn(sw,
"DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n",
header->data_crc32, crc);
}
return tb_drom_parse_entries(sw, USB4_DROM_HEADER_SIZE);
}
static int tb_drom_parse(struct tb_switch *sw, u16 size)
{
const struct tb_drom_header *header = (const void *)sw->drom;
int ret;
if (header->data_len + TB_DROM_DATA_START != size) {
tb_sw_warn(sw, "DROM size mismatch\n");
ret = -EIO;
goto err;
}
tb_sw_dbg(sw, "DROM version: %d\n", header->device_rom_revision);
switch (header->device_rom_revision) {
case 3:
ret = usb4_drom_parse(sw);
break;
default:
tb_sw_warn(sw, "DROM device_rom_revision %#x unknown\n",
header->device_rom_revision);
fallthrough;
case 1:
ret = tb_drom_parse_v1(sw);
break;
}
if (ret) {
tb_sw_warn(sw, "parsing DROM failed\n");
goto err;
}
return 0;
err:
tb_switch_drom_free(sw);
return ret;
}
static int tb_drom_host_read(struct tb_switch *sw)
{
u16 size;
if (tb_switch_is_usb4(sw)) {
usb4_switch_read_uid(sw, &sw->uid);
if (!usb4_copy_drom(sw, &size))
return tb_drom_parse(sw, size);
} else {
if (!tb_drom_copy_efi(sw, &size))
return tb_drom_parse(sw, size);
if (!tb_drom_copy_nvm(sw, &size))
return tb_drom_parse(sw, size);
tb_drom_read_uid_only(sw, &sw->uid);
}
return 0;
}
static int tb_drom_device_read(struct tb_switch *sw)
{
u16 size;
int ret;
if (tb_switch_is_usb4(sw)) {
usb4_switch_read_uid(sw, &sw->uid);
ret = usb4_copy_drom(sw, &size);
} else {
ret = tb_drom_bit_bang(sw, &size);
}
if (ret)
return ret;
return tb_drom_parse(sw, size);
}
/**
* tb_drom_read() - Copy DROM to sw->drom and parse it
* @sw: Router whose DROM to read and parse
*
* This function reads router DROM and if successful parses the entries and
* populates the fields in @sw accordingly. Can be called for any router
* generation.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_drom_read(struct tb_switch *sw)
{
if (sw->drom)
return 0;
if (!tb_route(sw))
return tb_drom_host_read(sw);
return tb_drom_device_read(sw);
}
File diff suppressed because it is too large Load Diff
+717
View File
@@ -0,0 +1,717 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt link controller support
*
* Copyright (C) 2019, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/delay.h>
#include "tb.h"
/**
* tb_lc_read_uuid() - Read switch UUID from link controller common register
* @sw: Switch whose UUID is read
* @uuid: UUID is placed here
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid)
{
if (!sw->cap_lc)
return -EINVAL;
return tb_sw_read(sw, uuid, TB_CFG_SWITCH, sw->cap_lc + TB_LC_FUSE, 4);
}
static int read_lc_desc(struct tb_switch *sw, u32 *desc)
{
if (!sw->cap_lc)
return -EINVAL;
return tb_sw_read(sw, desc, TB_CFG_SWITCH, sw->cap_lc + TB_LC_DESC, 1);
}
static int find_port_lc_cap(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int start, phys, ret, size;
u32 desc;
ret = read_lc_desc(sw, &desc);
if (ret)
return ret;
/* Start of port LC registers */
start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
phys = tb_phy_port_from_link(port->port);
return sw->cap_lc + start + phys * size;
}
/**
* tb_lc_reset_port() - Trigger downstream port reset through LC
* @port: Port that is reset
*
* Triggers downstream port reset through link controller registers.
* Only supports non-USB4 routers with link controller (that's
* Thunderbolt 2 and Thunderbolt 3).
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_reset_port(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int cap, ret;
u32 mode;
if (sw->generation < 2)
return -EINVAL;
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
if (ret)
return ret;
mode |= TB_LC_PORT_MODE_DPR;
ret = tb_sw_write(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
if (ret)
return ret;
fsleep(10000);
ret = tb_sw_read(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
if (ret)
return ret;
mode &= ~TB_LC_PORT_MODE_DPR;
return tb_sw_write(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
}
static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
{
bool upstream = tb_is_upstream_port(port);
struct tb_switch *sw = port->sw;
u32 ctrl, lane;
int cap, ret;
if (sw->generation < 2)
return 0;
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
/* Resolve correct lane */
if (port->port % 2)
lane = TB_LC_SX_CTRL_L1C;
else
lane = TB_LC_SX_CTRL_L2C;
if (configured) {
ctrl |= lane;
if (upstream)
ctrl |= TB_LC_SX_CTRL_UPSTREAM;
} else {
ctrl &= ~lane;
if (upstream)
ctrl &= ~TB_LC_SX_CTRL_UPSTREAM;
}
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
}
/**
* tb_lc_configure_port() - Let LC know about configured port
* @port: Port that is set as configured
*
* Sets the port configured for power management purposes.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_configure_port(struct tb_port *port)
{
return tb_lc_set_port_configured(port, true);
}
/**
* tb_lc_unconfigure_port() - Let LC know about unconfigured port
* @port: Port that is set as configured
*
* Sets the port unconfigured for power management purposes.
*
* Return: %0 on success, negative errno otherwise.
*/
void tb_lc_unconfigure_port(struct tb_port *port)
{
tb_lc_set_port_configured(port, false);
}
static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure)
{
struct tb_switch *sw = port->sw;
u32 ctrl, lane;
int cap, ret;
if (sw->generation < 2)
return 0;
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
/* Resolve correct lane */
if (port->port % 2)
lane = TB_LC_SX_CTRL_L1D;
else
lane = TB_LC_SX_CTRL_L2D;
if (configure)
ctrl |= lane;
else
ctrl &= ~lane;
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
}
/**
* tb_lc_configure_xdomain() - Inform LC that the link is XDomain
* @port: Switch downstream port connected to another host
*
* Sets the lane configured for XDomain accordingly so that LC knows
* about this.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_configure_xdomain(struct tb_port *port)
{
return tb_lc_set_xdomain_configured(port, true);
}
/**
* tb_lc_unconfigure_xdomain() - Unconfigure XDomain from port
* @port: Switch downstream port that was connected to another host
*
* Unsets the lane XDomain configuration.
*/
void tb_lc_unconfigure_xdomain(struct tb_port *port)
{
tb_lc_set_xdomain_configured(port, false);
}
/**
* tb_lc_start_lane_initialization() - Start lane initialization
* @port: Device router lane 0 adapter
*
* Starts lane initialization for @port after the router resumed from
* sleep. Should be called for those downstream lane adapters that were
* not connected (tb_lc_configure_port() was not called) before sleep.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_start_lane_initialization(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int ret, cap;
u32 ctrl;
if (!tb_route(sw))
return 0;
if (sw->generation < 2)
return 0;
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
ctrl |= TB_LC_SX_CTRL_SLI;
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
}
/**
* tb_lc_is_clx_supported() - Check whether CLx is supported by the lane adapter
* @port: Lane adapter
*
* TB_LC_LINK_ATTR_CPS bit reflects if the link supports CLx including
* active cables (if connected on the link).
*
* Return: %true if CLx is supported, %false otherwise.
*/
bool tb_lc_is_clx_supported(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int cap, ret;
u32 val;
cap = find_port_lc_cap(port);
if (cap < 0)
return false;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_ATTR, 1);
if (ret)
return false;
return !!(val & TB_LC_LINK_ATTR_CPS);
}
/**
* tb_lc_is_usb_plugged() - Is there USB device connected to port
* @port: Device router lane 0 adapter
*
* Return: %true if the @port has USB Type-C device connected, %false
* otherwise.
*/
bool tb_lc_is_usb_plugged(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int cap, ret;
u32 val;
if (sw->generation != 3)
return false;
cap = find_port_lc_cap(port);
if (cap < 0)
return false;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_CS_42, 1);
if (ret)
return false;
return !!(val & TB_LC_CS_42_USB_PLUGGED);
}
/**
* tb_lc_is_xhci_connected() - Is the internal xHCI connected
* @port: Device router lane 0 adapter
*
* Return: %true if the internal xHCI has been connected to
* @port, %false otherwise.
*/
bool tb_lc_is_xhci_connected(struct tb_port *port)
{
struct tb_switch *sw = port->sw;
int cap, ret;
u32 val;
if (sw->generation != 3)
return false;
cap = find_port_lc_cap(port);
if (cap < 0)
return false;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1);
if (ret)
return false;
return !!(val & TB_LC_LINK_REQ_XHCI_CONNECT);
}
static int __tb_lc_xhci_connect(struct tb_port *port, bool connect)
{
struct tb_switch *sw = port->sw;
int cap, ret;
u32 val;
if (sw->generation != 3)
return -EINVAL;
cap = find_port_lc_cap(port);
if (cap < 0)
return cap;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1);
if (ret)
return ret;
if (connect)
val |= TB_LC_LINK_REQ_XHCI_CONNECT;
else
val &= ~TB_LC_LINK_REQ_XHCI_CONNECT;
return tb_sw_write(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1);
}
/**
* tb_lc_xhci_connect() - Connect internal xHCI
* @port: Device router lane 0 adapter
*
* Tells LC to connect the internal xHCI to @port. Can be called for
* Thunderbolt 3 routers only.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_xhci_connect(struct tb_port *port)
{
int ret;
ret = __tb_lc_xhci_connect(port, true);
if (ret)
return ret;
tb_port_dbg(port, "xHCI connected\n");
return 0;
}
/**
* tb_lc_xhci_disconnect() - Disconnect internal xHCI
* @port: Device router lane 0 adapter
*
* Tells LC to disconnect the internal xHCI from @port. Can be called
* for Thunderbolt 3 routers only.
*/
void tb_lc_xhci_disconnect(struct tb_port *port)
{
__tb_lc_xhci_connect(port, false);
tb_port_dbg(port, "xHCI disconnected\n");
}
static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset,
unsigned int flags)
{
u32 ctrl;
int ret;
/*
* Enable wake on PCIe and USB4 (wake coming from another
* router).
*/
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH,
offset + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
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;
if (flags & TB_WAKE_ON_USB4)
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);
}
/**
* tb_lc_set_wake() - Enable/disable wake
* @sw: Switch whose wakes to configure
* @flags: Wakeup flags (%0 to disable)
*
* For each LC sets wake bits accordingly.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags)
{
int start, size, nlc, ret, i;
u32 desc;
if (sw->generation < 2)
return 0;
if (!tb_route(sw))
return 0;
ret = read_lc_desc(sw, &desc);
if (ret)
return ret;
/* Figure out number of link controllers */
nlc = desc & TB_LC_DESC_NLC_MASK;
start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
/* For each link controller set sleep bit */
for (i = 0; i < nlc; i++) {
unsigned int offset = sw->cap_lc + start + i * size;
ret = tb_lc_set_wake_one(sw, offset, flags);
if (ret)
return ret;
}
return 0;
}
/**
* tb_lc_set_sleep() - Inform LC that the switch is going to sleep
* @sw: Switch to set sleep
*
* Let the switch link controllers know that the switch is going to
* sleep.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_set_sleep(struct tb_switch *sw)
{
int start, size, nlc, ret, i;
u32 desc;
if (sw->generation < 2)
return 0;
ret = read_lc_desc(sw, &desc);
if (ret)
return ret;
/* Figure out number of link controllers */
nlc = desc & TB_LC_DESC_NLC_MASK;
start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
/* For each link controller set sleep bit */
for (i = 0; i < nlc; i++) {
unsigned int offset = sw->cap_lc + start + i * size;
u32 ctrl;
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH,
offset + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
ctrl |= TB_LC_SX_CTRL_SLP;
ret = tb_sw_write(sw, &ctrl, TB_CFG_SWITCH,
offset + TB_LC_SX_CTRL, 1);
if (ret)
return ret;
}
return 0;
}
/**
* tb_lc_lane_bonding_possible() - Is lane bonding possible towards switch
* @sw: Switch to check
*
* Checks whether conditions for lane bonding from parent to @sw are
* possible.
*
* Return: %true if lane bonding is possible, %false otherwise.
*/
bool tb_lc_lane_bonding_possible(struct tb_switch *sw)
{
struct tb_port *up;
int cap, ret;
u32 val;
if (sw->generation < 2)
return false;
up = tb_upstream_port(sw);
cap = find_port_lc_cap(up);
if (cap < 0)
return false;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_PORT_ATTR, 1);
if (ret)
return false;
return !!(val & TB_LC_PORT_ATTR_BE);
}
static int tb_lc_dp_sink_from_port(const struct tb_switch *sw,
struct tb_port *in)
{
struct tb_port *port;
/* The first DP IN port is sink 0 and second is sink 1 */
tb_switch_for_each_port(sw, port) {
if (tb_port_is_dpin(port))
return in != port;
}
return -EINVAL;
}
static int tb_lc_dp_sink_available(struct tb_switch *sw, int sink)
{
u32 val, alloc;
int ret;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
if (ret)
return ret;
/*
* Sink is available for CM/SW to use if the allocation value is
* either 0 or 1.
*/
if (!sink) {
alloc = val & TB_LC_SNK_ALLOCATION_SNK0_MASK;
if (!alloc || alloc == TB_LC_SNK_ALLOCATION_SNK0_CM)
return 0;
} else {
alloc = (val & TB_LC_SNK_ALLOCATION_SNK1_MASK) >>
TB_LC_SNK_ALLOCATION_SNK1_SHIFT;
if (!alloc || alloc == TB_LC_SNK_ALLOCATION_SNK1_CM)
return 0;
}
return -EBUSY;
}
/**
* tb_lc_dp_sink_query() - Is DP sink available for DP IN port
* @sw: Switch whose DP sink is queried
* @in: DP IN port to check
*
* Queries through LC SNK_ALLOCATION registers whether DP sink is available
* for the given DP IN port or not.
*
* Return: %true if DP sink is available, %false otherwise.
*/
bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in)
{
int sink;
/*
* For older generations sink is always available as there is no
* allocation mechanism.
*/
if (sw->generation < 3)
return true;
sink = tb_lc_dp_sink_from_port(sw, in);
if (sink < 0)
return false;
return !tb_lc_dp_sink_available(sw, sink);
}
/**
* tb_lc_dp_sink_alloc() - Allocate DP sink
* @sw: Switch whose DP sink is allocated
* @in: DP IN port the DP sink is allocated for
*
* Allocate DP sink for @in via LC SNK_ALLOCATION registers.
*
* Return:
* * %0 - If the resource is available and allocation is successful.
* * %-EBUSY - If resource is not available.
* * Negative errno - Another error occurred.
*/
int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in)
{
int ret, sink;
u32 val;
if (sw->generation < 3)
return 0;
sink = tb_lc_dp_sink_from_port(sw, in);
if (sink < 0)
return sink;
ret = tb_lc_dp_sink_available(sw, sink);
if (ret)
return ret;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
if (ret)
return ret;
if (!sink) {
val &= ~TB_LC_SNK_ALLOCATION_SNK0_MASK;
val |= TB_LC_SNK_ALLOCATION_SNK0_CM;
} else {
val &= ~TB_LC_SNK_ALLOCATION_SNK1_MASK;
val |= TB_LC_SNK_ALLOCATION_SNK1_CM <<
TB_LC_SNK_ALLOCATION_SNK1_SHIFT;
}
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH,
sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
if (ret)
return ret;
tb_port_dbg(in, "sink %d allocated\n", sink);
return 0;
}
/**
* tb_lc_dp_sink_dealloc() - De-allocate DP sink
* @sw: Switch whose DP sink is de-allocated
* @in: DP IN port whose DP sink is de-allocated
*
* De-allocate DP sink from @in using LC SNK_ALLOCATION registers.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in)
{
int ret, sink;
u32 val;
if (sw->generation < 3)
return 0;
sink = tb_lc_dp_sink_from_port(sw, in);
if (sink < 0)
return sink;
/* Needs to be owned by CM/SW */
ret = tb_lc_dp_sink_available(sw, sink);
if (ret)
return ret;
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
if (ret)
return ret;
if (!sink)
val &= ~TB_LC_SNK_ALLOCATION_SNK0_MASK;
else
val &= ~TB_LC_SNK_ALLOCATION_SNK1_MASK;
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH,
sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
if (ret)
return ret;
tb_port_dbg(in, "sink %d de-allocated\n", sink);
return 0;
}
/**
* tb_lc_force_power() - Forces LC to be powered on
* @sw: Thunderbolt switch
*
* This is useful to let authentication cycle pass even without
* a Thunderbolt link present.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_lc_force_power(struct tb_switch *sw)
{
u32 in = 0xffff;
return tb_sw_write(sw, &in, TB_CFG_SWITCH, TB_LC_POWER, 1);
}
File diff suppressed because it is too large Load Diff
+132
View File
@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt driver - NHI driver
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#ifndef DSL3510_H_
#define DSL3510_H_
#include <linux/thunderbolt.h>
enum nhi_fw_mode {
NHI_FW_SAFE_MODE,
NHI_FW_AUTH_MODE,
NHI_FW_EP_MODE,
NHI_FW_CM_MODE,
};
enum nhi_mailbox_cmd {
NHI_MAILBOX_SAVE_DEVS = 0x05,
NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
NHI_MAILBOX_DRV_UNLOADS = 0x07,
NHI_MAILBOX_DISCONNECT_PA = 0x10,
NHI_MAILBOX_DISCONNECT_PB = 0x11,
NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
};
int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data);
enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
void nhi_enable_int_throttling(struct tb_nhi *nhi);
void nhi_disable_interrupts(struct tb_nhi *nhi);
void nhi_interrupt_work(struct work_struct *work);
irqreturn_t nhi_msi(int irq, void *data);
irqreturn_t ring_msix(int irq, void *data);
int nhi_probe(struct tb_nhi *nhi);
void nhi_shutdown(struct tb_nhi *nhi);
extern const struct dev_pm_ops nhi_pm_ops;
/**
* struct tb_nhi_ops - NHI specific optional operations
* @init: NHI specific initialization
* @suspend_noirq: NHI specific suspend_noirq hook
* @resume_noirq: NHI specific resume_noirq hook
* @runtime_suspend: NHI specific runtime_suspend hook
* @runtime_resume: NHI specific runtime_resume hook
* @shutdown: NHI specific shutdown
* @pre_nvm_auth: hook to run before Thunderbolt 3 NVM authentication
* @post_nvm_auth: hook to run after Thunderbolt 3 NVM authentication
* @request_ring_irq: NHI specific interrupt retrieval hook
* @release_ring_irq: NHI specific interrupt release hook
* @is_present: Whether the device is currently present on the parent bus
* @init_interrupts: NHI specific interrupt initialization hook
*/
struct tb_nhi_ops {
int (*init)(struct tb_nhi *nhi);
int (*suspend_noirq)(struct tb_nhi *nhi, bool wakeup);
int (*resume_noirq)(struct tb_nhi *nhi);
int (*runtime_suspend)(struct tb_nhi *nhi);
int (*runtime_resume)(struct tb_nhi *nhi);
void (*shutdown)(struct tb_nhi *nhi);
void (*pre_nvm_auth)(struct tb_nhi *nhi);
void (*post_nvm_auth)(struct tb_nhi *nhi);
int (*request_ring_irq)(struct tb_ring *ring, bool no_suspend);
void (*release_ring_irq)(struct tb_ring *ring);
bool (*is_present)(struct tb_nhi *nhi);
int (*init_interrupts)(struct tb_nhi *nhi);
};
/*
* PCI IDs used in this driver from Win Ridge forward. There is no
* need for the PCI quirk anymore as we will use ICM also on Apple
* hardware.
*/
#define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_2C_NHI 0x1134
#define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_4C_NHI 0x1137
#define PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_NHI 0x157d
#define PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_BRIDGE 0x157e
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI 0x15bf
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE 0x15c0
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI 0x15d2
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE 0x15d3
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI 0x15d9
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE 0x15da
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI 0x15dc
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI 0x15dd
#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI 0x15de
#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE 0x15e7
#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI 0x15e8
#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_WCL_NHI0 0x4d33
#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI 0x5781
#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI 0x5784
#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE 0x5786
#define PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE 0x57a4
#define PCI_DEVICE_ID_INTEL_MTL_M_NHI0 0x7eb2
#define PCI_DEVICE_ID_INTEL_MTL_P_NHI0 0x7ec2
#define PCI_DEVICE_ID_INTEL_MTL_P_NHI1 0x7ec3
#define PCI_DEVICE_ID_INTEL_ICL_NHI1 0x8a0d
#define PCI_DEVICE_ID_INTEL_ICL_NHI0 0x8a17
#define PCI_DEVICE_ID_INTEL_TGL_NHI0 0x9a1b
#define PCI_DEVICE_ID_INTEL_TGL_NHI1 0x9a1d
#define PCI_DEVICE_ID_INTEL_TGL_H_NHI0 0x9a1f
#define PCI_DEVICE_ID_INTEL_TGL_H_NHI1 0x9a21
#define PCI_DEVICE_ID_INTEL_RPL_NHI0 0xa73e
#define PCI_DEVICE_ID_INTEL_RPL_NHI1 0xa76d
#define PCI_DEVICE_ID_INTEL_LNL_NHI0 0xa833
#define PCI_DEVICE_ID_INTEL_LNL_NHI1 0xa834
#define PCI_DEVICE_ID_INTEL_PTL_M_NHI0 0xe333
#define PCI_DEVICE_ID_INTEL_PTL_M_NHI1 0xe334
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433
#define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434
#define PCI_CLASS_SERIAL_USB_USB4 0x0c0340
/* Host interface quirks */
#define QUIRK_AUTO_CLEAR_INT BIT(0)
#define QUIRK_E2E BIT(1)
/*
* Minimal number of vectors when we use MSI-X. Two for control channel
* Rx/Tx and the rest four are for cross domain DMA paths.
*/
#define MSIX_MIN_VECS 6
#define MSIX_MAX_VECS 16
#endif
+180
View File
@@ -0,0 +1,180 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt driver - NHI registers
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#ifndef NHI_REGS_H_
#define NHI_REGS_H_
#include <linux/types.h>
enum ring_flags {
RING_FLAG_ISOCH_ENABLE = 1 << 27, /* TX only? */
RING_FLAG_E2E_FLOW_CONTROL = 1 << 28,
RING_FLAG_PCI_NO_SNOOP = 1 << 29,
RING_FLAG_RAW = 1 << 30, /* ignore EOF/SOF mask, include checksum */
RING_FLAG_ENABLE = 1 << 31,
};
/**
* struct ring_desc - TX/RX ring entry
* @phys: DMA mapped address of the frame
* @length: Size of the ring
* @eof: End of frame protocol defined field
* @sof: Start of frame protocol defined field
* @flags: Ring descriptor flags
* @time: Fill with zero
*
* For TX set length/eof/sof.
* For RX length/eof/sof are set by the NHI.
*/
struct ring_desc {
u64 phys;
u32 length:12;
u32 eof:4;
u32 sof:4;
enum ring_desc_flags flags:12;
u32 time; /* write zero */
} __packed;
/* NHI registers in bar 0 */
/*
* 16 bytes per entry, one entry for every hop (REG_CAPS)
* 00: physical pointer to an array of struct ring_desc
* 08: ring tail (set by NHI)
* 10: ring head (index of first non posted descriptor)
* 12: descriptor count
*/
#define REG_TX_RING_BASE 0x00000
/*
* 16 bytes per entry, one entry for every hop (REG_CAPS)
* 00: physical pointer to an array of struct ring_desc
* 08: ring head (index of first not posted descriptor)
* 10: ring tail (set by NHI)
* 12: descriptor count
* 14: max frame sizes (anything larger than 0x100 has no effect)
*/
#define REG_RX_RING_BASE 0x08000
/*
* 32 bytes per entry, one entry for every hop (REG_CAPS)
* 00: enum_ring_flags
* 04: isoch time stamp ?? (write 0)
* ..: unknown
*/
#define REG_TX_OPTIONS_BASE 0x19800
/*
* 32 bytes per entry, one entry for every hop (REG_CAPS)
* 00: enum ring_flags
* If RING_FLAG_E2E_FLOW_CONTROL is set then bits 13-23 must be set to
* the corresponding TX hop id.
* 04: EOF/SOF mask (ignored for RING_FLAG_RAW rings)
* ..: unknown
*/
#define REG_RX_OPTIONS_BASE 0x29800
#define REG_RX_OPTIONS_E2E_HOP_MASK GENMASK(22, 12)
#define REG_RX_OPTIONS_E2E_HOP_SHIFT 12
/*
* three bitfields: tx, rx, rx overflow
* Every bitfield contains one bit for every hop (REG_CAPS).
* New interrupts are fired only after ALL registers have been
* read (even those containing only disabled rings).
*/
#define REG_RING_NOTIFY_BASE 0x37800
#define RING_NOTIFY_REG_COUNT(nhi) ((31 + 3 * nhi->hop_count) / 32)
#define REG_RING_INT_CLEAR 0x37808
/*
* two bitfields: rx, tx
* Both bitfields contains one bit for every hop (REG_CAPS). To
* enable/disable interrupts set/clear the corresponding bits.
*/
#define REG_RING_INTERRUPT_BASE 0x38200
#define RING_INTERRUPT_REG_COUNT(nhi) ((31 + 2 * nhi->hop_count) / 32)
#define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208
#define REG_INT_THROTTLING_RATE 0x38c00
#define REG_INT_THROTTLING_RATE_INTERVAL_MASK GENMASK(15, 0)
/* Interrupt Vector Allocation */
#define REG_INT_VEC_ALLOC_BASE 0x38c40
#define REG_INT_VEC_ALLOC_BITS 4
#define REG_INT_VEC_ALLOC_MASK GENMASK(3, 0)
#define REG_INT_VEC_ALLOC_REGS (32 / REG_INT_VEC_ALLOC_BITS)
/* The last 11 bits contain the number of hops supported by the NHI port. */
#define REG_CAPS 0x39640
#define REG_CAPS_VERSION_MASK GENMASK(23, 16)
#define REG_CAPS_VERSION_2 0x40
#define REG_DMA_MISC 0x39864
#define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2)
#define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17)
#define REG_RESET 0x39898
#define REG_RESET_HRR BIT(0)
#define REG_INMAIL_DATA 0x39900
#define REG_INMAIL_CMD 0x39904
#define REG_INMAIL_CMD_MASK GENMASK(7, 0)
#define REG_INMAIL_ERROR BIT(30)
#define REG_INMAIL_OP_REQUEST BIT(31)
#define REG_OUTMAIL_CMD 0x3990c
#define REG_OUTMAIL_CMD_OPMODE_SHIFT 8
#define REG_OUTMAIL_CMD_OPMODE_MASK GENMASK(11, 8)
#define REG_FW_STS 0x39944
#define REG_FW_STS_NVM_AUTH_DONE BIT(31)
#define REG_FW_STS_CIO_RESET_REQ BIT(30)
#define REG_FW_STS_ICM_EN_CPU BIT(2)
#define REG_FW_STS_ICM_EN_INVERT BIT(1)
#define REG_FW_STS_ICM_EN BIT(0)
/* ICL NHI VSEC registers */
/* FW ready */
#define VS_CAP_9 0xc8
#define VS_CAP_9_FW_READY BIT(31)
/* UUID */
#define VS_CAP_10 0xcc
#define VS_CAP_11 0xd0
/* LTR */
#define VS_CAP_15 0xe0
#define VS_CAP_16 0xe4
/* TBT2PCIe */
#define VS_CAP_18 0xec
#define VS_CAP_18_DONE BIT(0)
/* PCIe2TBT */
#define VS_CAP_19 0xf0
#define VS_CAP_19_VALID BIT(0)
#define VS_CAP_19_CMD_SHIFT 1
#define VS_CAP_19_CMD_MASK GENMASK(7, 1)
/* Force power */
#define VS_CAP_22 0xfc
#define VS_CAP_22_FORCE_POWER BIT(1)
#define VS_CAP_22_DMA_DELAY_MASK GENMASK(31, 24)
#define VS_CAP_22_DMA_DELAY_SHIFT 24
/**
* enum icl_lc_mailbox_cmd - ICL specific LC mailbox commands
* @ICL_LC_GO2SX: Ask LC to enter Sx without wake
* @ICL_LC_GO2SX_NO_WAKE: Ask LC to enter Sx with wake
* @ICL_LC_PREPARE_FOR_RESET: Prepare LC for reset
*/
enum icl_lc_mailbox_cmd {
ICL_LC_GO2SX = 0x02,
ICL_LC_GO2SX_NO_WAKE = 0x03,
ICL_LC_PREPARE_FOR_RESET = 0x21,
};
#endif
+644
View File
@@ -0,0 +1,644 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVM helpers
*
* Copyright (C) 2020, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "tb.h"
#define NVM_MIN_SIZE SZ_32K
#define NVM_MAX_SIZE SZ_1M
#define NVM_DATA_DWORDS 16
/* Intel specific NVM offsets */
#define INTEL_NVM_DEVID 0x05
#define INTEL_NVM_VERSION 0x08
#define INTEL_NVM_CSS 0x10
#define INTEL_NVM_FLASH_SIZE 0x45
/* ASMedia specific NVM offsets */
#define ASMEDIA_NVM_DATE 0x1c
#define ASMEDIA_NVM_VERSION 0x28
static DEFINE_IDA(nvm_ida);
/**
* struct tb_nvm_vendor_ops - Vendor specific NVM operations
* @read_version: Reads out NVM version from the flash
* @validate: Validates the NVM image before update (optional)
* @write_headers: Writes headers before the rest of the image (optional)
*/
struct tb_nvm_vendor_ops {
int (*read_version)(struct tb_nvm *nvm);
int (*validate)(struct tb_nvm *nvm);
int (*write_headers)(struct tb_nvm *nvm);
};
/**
* struct tb_nvm_vendor - Vendor to &struct tb_nvm_vendor_ops mapping
* @vendor: Vendor ID
* @vops: Vendor specific NVM operations
*
* Maps vendor ID to NVM vendor operations. If there is no mapping then
* NVM firmware upgrade is disabled for the device.
*/
struct tb_nvm_vendor {
u16 vendor;
const struct tb_nvm_vendor_ops *vops;
};
static int intel_switch_nvm_version(struct tb_nvm *nvm)
{
struct tb_switch *sw = tb_to_switch(nvm->dev);
u32 val, nvm_size, hdr_size;
int ret;
/*
* If the switch is in safe-mode the only accessible portion of
* the NVM is the non-active one where userspace is expected to
* write new functional NVM.
*/
if (sw->safe_mode)
return 0;
ret = tb_switch_nvm_read(sw, INTEL_NVM_FLASH_SIZE, &val, sizeof(val));
if (ret)
return ret;
hdr_size = sw->generation < 3 ? SZ_8K : SZ_16K;
nvm_size = (SZ_1M << (val & 7)) / 8;
nvm_size = (nvm_size - hdr_size) / 2;
ret = tb_switch_nvm_read(sw, INTEL_NVM_VERSION, &val, sizeof(val));
if (ret)
return ret;
nvm->major = (val >> 16) & 0xff;
nvm->minor = (val >> 8) & 0xff;
nvm->active_size = nvm_size;
return 0;
}
static int intel_switch_nvm_validate(struct tb_nvm *nvm)
{
struct tb_switch *sw = tb_to_switch(nvm->dev);
unsigned int image_size, hdr_size;
u16 ds_size, device_id;
u8 *buf = nvm->buf;
image_size = nvm->buf_data_size;
/*
* 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 + INTEL_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;
if (sw->safe_mode)
return 0;
/*
* Make sure the device ID in the image matches the one
* we read from the switch config space.
*/
device_id = *(u16 *)(buf + hdr_size + INTEL_NVM_DEVID);
if (device_id != sw->config.device_id)
return -EINVAL;
/* Skip headers in the image */
nvm->buf_data_start = buf + hdr_size;
nvm->buf_data_size = image_size - hdr_size;
return 0;
}
static int intel_switch_nvm_write_headers(struct tb_nvm *nvm)
{
struct tb_switch *sw = tb_to_switch(nvm->dev);
if (sw->generation < 3) {
int ret;
/* Write CSS headers first */
ret = dma_port_flash_write(sw->dma_port,
DMA_PORT_CSS_ADDRESS, nvm->buf + INTEL_NVM_CSS,
DMA_PORT_CSS_MAX_SIZE);
if (ret)
return ret;
}
return 0;
}
static const struct tb_nvm_vendor_ops intel_switch_nvm_ops = {
.read_version = intel_switch_nvm_version,
.validate = intel_switch_nvm_validate,
.write_headers = intel_switch_nvm_write_headers,
};
static int asmedia_switch_nvm_version(struct tb_nvm *nvm)
{
struct tb_switch *sw = tb_to_switch(nvm->dev);
u32 val;
int ret;
ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_VERSION, &val, sizeof(val));
if (ret)
return ret;
nvm->major = (val << 16) & 0xff0000;
nvm->major |= val & 0x00ff00;
nvm->major |= (val >> 16) & 0x0000ff;
ret = tb_switch_nvm_read(sw, ASMEDIA_NVM_DATE, &val, sizeof(val));
if (ret)
return ret;
nvm->minor = (val << 16) & 0xff0000;
nvm->minor |= val & 0x00ff00;
nvm->minor |= (val >> 16) & 0x0000ff;
/* ASMedia NVM size is fixed to 512k */
nvm->active_size = SZ_512K;
return 0;
}
static const struct tb_nvm_vendor_ops asmedia_switch_nvm_ops = {
.read_version = asmedia_switch_nvm_version,
};
/* Router vendor NVM support table */
static const struct tb_nvm_vendor switch_nvm_vendors[] = {
{ 0x174c, &asmedia_switch_nvm_ops },
{ PCI_VENDOR_ID_INTEL, &intel_switch_nvm_ops },
{ 0x8087, &intel_switch_nvm_ops },
};
static int intel_retimer_nvm_version(struct tb_nvm *nvm)
{
struct tb_retimer *rt = tb_to_retimer(nvm->dev);
u32 val, nvm_size;
int ret;
ret = tb_retimer_nvm_read(rt, INTEL_NVM_VERSION, &val, sizeof(val));
if (ret)
return ret;
nvm->major = (val >> 16) & 0xff;
nvm->minor = (val >> 8) & 0xff;
ret = tb_retimer_nvm_read(rt, INTEL_NVM_FLASH_SIZE, &val, sizeof(val));
if (ret)
return ret;
nvm_size = (SZ_1M << (val & 7)) / 8;
nvm_size = (nvm_size - SZ_16K) / 2;
nvm->active_size = nvm_size;
return 0;
}
static int intel_retimer_nvm_validate(struct tb_nvm *nvm)
{
struct tb_retimer *rt = tb_to_retimer(nvm->dev);
unsigned int image_size, hdr_size;
u8 *buf = nvm->buf;
u16 ds_size, device;
image_size = nvm->buf_data_size;
/*
* 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 + INTEL_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 + INTEL_NVM_DEVID);
if (device != rt->device)
return -EINVAL;
/* Skip headers in the image */
nvm->buf_data_start = buf + hdr_size;
nvm->buf_data_size = image_size - hdr_size;
return 0;
}
static const struct tb_nvm_vendor_ops intel_retimer_nvm_ops = {
.read_version = intel_retimer_nvm_version,
.validate = intel_retimer_nvm_validate,
};
/* Retimer vendor NVM support table */
static const struct tb_nvm_vendor retimer_nvm_vendors[] = {
{ 0x8087, &intel_retimer_nvm_ops },
};
/**
* tb_nvm_alloc() - Allocate new NVM structure
* @dev: Device owning the NVM
*
* Allocates new NVM structure with unique @id and returns it.
*
* Return:
* * Pointer to &struct tb_nvm - On success.
* * %-EOPNOTSUPP - If the NVM format of the @dev is not known by the
* kernel.
* * %ERR_PTR - In case of failure.
*/
struct tb_nvm *tb_nvm_alloc(struct device *dev)
{
const struct tb_nvm_vendor_ops *vops = NULL;
struct tb_nvm *nvm;
int ret, i;
if (tb_is_switch(dev)) {
const struct tb_switch *sw = tb_to_switch(dev);
for (i = 0; i < ARRAY_SIZE(switch_nvm_vendors); i++) {
const struct tb_nvm_vendor *v = &switch_nvm_vendors[i];
if (v->vendor == sw->config.vendor_id) {
vops = v->vops;
break;
}
}
if (!vops) {
tb_sw_dbg(sw, "router NVM format of vendor %#x unknown\n",
sw->config.vendor_id);
return ERR_PTR(-EOPNOTSUPP);
}
} else if (tb_is_retimer(dev)) {
const struct tb_retimer *rt = tb_to_retimer(dev);
for (i = 0; i < ARRAY_SIZE(retimer_nvm_vendors); i++) {
const struct tb_nvm_vendor *v = &retimer_nvm_vendors[i];
if (v->vendor == rt->vendor) {
vops = v->vops;
break;
}
}
if (!vops) {
dev_dbg(dev, "retimer NVM format of vendor %#x unknown\n",
rt->vendor);
return ERR_PTR(-EOPNOTSUPP);
}
} else {
return ERR_PTR(-EOPNOTSUPP);
}
nvm = kzalloc_obj(*nvm);
if (!nvm)
return ERR_PTR(-ENOMEM);
ret = ida_alloc(&nvm_ida, GFP_KERNEL);
if (ret < 0) {
kfree(nvm);
return ERR_PTR(ret);
}
nvm->id = ret;
nvm->dev = dev;
nvm->vops = vops;
return nvm;
}
/**
* tb_nvm_read_version() - Read and populate NVM version
* @nvm: NVM structure
*
* Uses vendor specific means to read and fill out the existing
* active NVM version.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_read_version(struct tb_nvm *nvm)
{
const struct tb_nvm_vendor_ops *vops = nvm->vops;
if (vops && vops->read_version)
return vops->read_version(nvm);
return -EOPNOTSUPP;
}
/**
* tb_nvm_validate() - Validate new NVM image
* @nvm: NVM structure
*
* Runs vendor specific validation over the new NVM image. As a
* side effect, updates @nvm->buf_data_start and @nvm->buf_data_size
* fields to match the actual data to be written to the NVM.
*
* Return: %0 on successful validation, negative errno otherwise.
*/
int tb_nvm_validate(struct tb_nvm *nvm)
{
const struct tb_nvm_vendor_ops *vops = nvm->vops;
unsigned int image_size;
u8 *buf = nvm->buf;
if (!buf)
return -EINVAL;
if (!vops)
return -EOPNOTSUPP;
/* Just do basic image size checks */
image_size = nvm->buf_data_size;
if (image_size < NVM_MIN_SIZE || image_size > NVM_MAX_SIZE)
return -EINVAL;
/*
* Set the default data start in the buffer. The validate method
* below can change this if needed.
*/
nvm->buf_data_start = buf;
return vops->validate ? vops->validate(nvm) : 0;
}
/**
* tb_nvm_write_headers() - Write headers before the rest of the image
* @nvm: NVM structure
*
* If the vendor NVM format requires writing headers before the rest of
* the image, this function does that. Can be called even if the device
* does not need this.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_write_headers(struct tb_nvm *nvm)
{
const struct tb_nvm_vendor_ops *vops = nvm->vops;
return vops->write_headers ? vops->write_headers(nvm) : 0;
}
/**
* tb_nvm_add_active() - Adds active NVMem device to NVM
* @nvm: NVM structure
* @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.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_add_active(struct tb_nvm *nvm, 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 = nvm->active_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.
*
* Return:
* * %0 - On success.
* * %-ENOMEM - If buffer allocation failed.
* * Negative errno - Another error occurred.
*/
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->flushed = false;
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
* @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.
* The size of the NVMem device is set to %NVM_MAX_SIZE.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_nvm_add_non_active(struct tb_nvm *nvm, 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 = NVM_MAX_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) {
nvmem_unregister(nvm->non_active);
nvmem_unregister(nvm->active);
vfree(nvm->buf);
ida_free(&nvm_ida, nvm->id);
}
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.
*
* Return: %0 on success, 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 passed to @write_block
*
* This is generic function that writes data to NVM or NVM like device.
*
* Return: %0 on success, 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);
}
+631
View File
@@ -0,0 +1,631 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt driver - path/tunnel functionality
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2019, Intel Corporation
*/
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include "tb.h"
static void tb_dump_hop(const struct tb_path_hop *hop, const struct tb_regs_hop *regs)
{
const struct tb_port *port = hop->in_port;
tb_port_dbg(port, " In HopID: %d => Out port: %d Out HopID: %d\n",
hop->in_hop_index, regs->out_port, regs->next_hop);
tb_port_dbg(port, " Weight: %d Priority: %d Credits: %d Drop: %d PM: %d\n",
regs->weight, regs->priority, regs->initial_credits,
regs->drop_packages, regs->pmps);
tb_port_dbg(port, " Counter enabled: %d Counter index: %d\n",
regs->counter_enable, regs->counter);
tb_port_dbg(port, " Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n",
regs->ingress_fc, regs->egress_fc,
regs->ingress_shared_buffer, regs->egress_shared_buffer);
tb_port_dbg(port, " Unknown1: %#x Unknown2: %#x Unknown3: %#x\n",
regs->unknown1, regs->unknown2, regs->unknown3);
}
static struct tb_port *tb_path_find_dst_port(struct tb_port *src, int src_hopid,
int dst_hopid)
{
struct tb_port *port, *out_port = NULL;
struct tb_regs_hop hop;
struct tb_switch *sw;
int i, ret, hopid;
hopid = src_hopid;
port = src;
for (i = 0; port && i < TB_PATH_MAX_HOPS; i++) {
sw = port->sw;
ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hopid, 2);
if (ret) {
tb_port_warn(port, "failed to read path at %d\n", hopid);
return NULL;
}
if (!hop.enable)
return NULL;
out_port = &sw->ports[hop.out_port];
hopid = hop.next_hop;
port = out_port->remote;
}
return out_port && hopid == dst_hopid ? out_port : NULL;
}
static int tb_path_find_src_hopid(struct tb_port *src,
const struct tb_port *dst, int dst_hopid)
{
struct tb_port *out;
int i;
for (i = TB_PATH_MIN_HOPID; i <= src->config.max_in_hop_id; i++) {
out = tb_path_find_dst_port(src, i, dst_hopid);
if (out == dst)
return i;
}
return 0;
}
/**
* tb_path_discover() - Discover a path
* @src: First input port of a path
* @src_hopid: Starting HopID of a path (%-1 if don't care)
* @dst: Expected destination port of the path (%NULL if don't care)
* @dst_hopid: HopID to the @dst (%-1 if don't care)
* @last: Last port is filled here if not %NULL
* @name: Name of the path
* @alloc_hopid: Allocate HopIDs for the ports
*
* Follows a path starting from @src and @src_hopid to the last output
* port of the path. Allocates HopIDs for the visited ports (if
* @alloc_hopid is true). Call tb_path_free() to release the path and
* allocated HopIDs when the path is not needed anymore.
*
* Note function discovers also incomplete paths so caller should check
* that the @dst port is the expected one. If it is not, the path can be
* cleaned up by calling tb_path_deactivate() before tb_path_free().
*
* Return: Pointer to &struct tb_path, %NULL in case of failure.
*/
struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid,
struct tb_port *dst, int dst_hopid,
struct tb_port **last, const char *name,
bool alloc_hopid)
{
struct tb_port *out_port;
struct tb_regs_hop hop;
struct tb_path *path;
struct tb_switch *sw;
struct tb_port *p;
size_t num_hops;
int ret, i, h;
if (src_hopid < 0 && dst) {
/*
* For incomplete paths the intermediate HopID can be
* different from the one used by the protocol adapter
* so in that case find a path that ends on @dst with
* matching @dst_hopid. That should give us the correct
* HopID for the @src.
*/
src_hopid = tb_path_find_src_hopid(src, dst, dst_hopid);
if (!src_hopid)
return NULL;
}
p = src;
h = src_hopid;
num_hops = 0;
for (i = 0; p && i < TB_PATH_MAX_HOPS; i++) {
sw = p->sw;
ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2);
if (ret) {
tb_port_warn(p, "failed to read path at %d\n", h);
return NULL;
}
/* If the hop is not enabled we got an incomplete path */
if (!hop.enable)
break;
out_port = &sw->ports[hop.out_port];
if (last)
*last = out_port;
h = hop.next_hop;
p = out_port->remote;
num_hops++;
}
path = kzalloc_flex(*path, hops, num_hops);
if (!path)
return NULL;
path->path_length = num_hops;
path->name = name;
path->tb = src->sw->tb;
path->activated = true;
path->alloc_hopid = alloc_hopid;
tb_dbg(path->tb, "discovering %s path starting from %llx:%u\n",
path->name, tb_route(src->sw), src->port);
p = src;
h = src_hopid;
for (i = 0; i < num_hops; i++) {
int next_hop;
sw = p->sw;
ret = tb_port_read(p, &hop, TB_CFG_HOPS, 2 * h, 2);
if (ret) {
tb_port_warn(p, "failed to read path at %d\n", h);
goto err;
}
if (alloc_hopid && tb_port_alloc_in_hopid(p, h, h) < 0)
goto err;
out_port = &sw->ports[hop.out_port];
next_hop = hop.next_hop;
if (alloc_hopid &&
tb_port_alloc_out_hopid(out_port, next_hop, next_hop) < 0) {
tb_port_release_in_hopid(p, h);
goto err;
}
path->hops[i].in_port = p;
path->hops[i].in_hop_index = h;
path->hops[i].in_counter_index = -1;
path->hops[i].out_port = out_port;
path->hops[i].next_hop_index = next_hop;
tb_dump_hop(&path->hops[i], &hop);
h = next_hop;
p = out_port->remote;
}
tb_dbg(path->tb, "path discovery complete\n");
return path;
err:
tb_port_warn(src, "failed to discover path starting at HopID %d\n",
src_hopid);
tb_path_free(path);
return NULL;
}
/**
* tb_path_alloc() - allocate a thunderbolt path between two ports
* @tb: Domain pointer
* @src: Source port of the path
* @src_hopid: HopID used for the first ingress port in the path
* @dst: Destination port of the path
* @dst_hopid: HopID used for the last egress port in the path
* @link_nr: Preferred link if there are dual links on the path
* @name: Name of the path
*
* Creates path between two ports starting with given @src_hopid. Reserves
* HopIDs for each port (they can be different from @src_hopid depending on
* how many HopIDs each port already have reserved). If there are dual
* links on the path, prioritizes using @link_nr but takes into account
* that the lanes may be bonded.
*
* Return: Pointer to &struct tb_path, %NULL in case of failure.
*/
struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid,
struct tb_port *dst, int dst_hopid, int link_nr,
const char *name)
{
struct tb_port *in_port, *out_port, *first_port, *last_port;
int in_hopid, out_hopid;
struct tb_path *path;
size_t num_hops;
int i, ret;
first_port = last_port = NULL;
i = 0;
tb_for_each_port_on_path(src, dst, in_port) {
if (!first_port)
first_port = in_port;
last_port = in_port;
i++;
}
/* Check that src and dst are reachable */
if (first_port != src || last_port != dst)
return NULL;
/* Each hop takes two ports */
num_hops = i / 2;
path = kzalloc_flex(*path, hops, num_hops);
if (!path)
return NULL;
path->path_length = num_hops;
path->alloc_hopid = true;
in_hopid = src_hopid;
out_port = NULL;
for (i = 0; i < num_hops; i++) {
in_port = tb_next_port_on_path(src, dst, out_port);
if (!in_port)
goto err;
/* When lanes are bonded primary link must be used */
if (!in_port->bonded && in_port->dual_link_port &&
in_port->link_nr != link_nr)
in_port = in_port->dual_link_port;
ret = tb_port_alloc_in_hopid(in_port, in_hopid, in_hopid);
if (ret < 0)
goto err;
in_hopid = ret;
out_port = tb_next_port_on_path(src, dst, in_port);
if (!out_port)
goto err;
/*
* Pick up right port when going from non-bonded to
* bonded or from bonded to non-bonded.
*/
if (out_port->dual_link_port) {
if (!in_port->bonded && out_port->bonded &&
out_port->link_nr) {
/*
* Use primary link when going from
* non-bonded to bonded.
*/
out_port = out_port->dual_link_port;
} else if (!out_port->bonded &&
out_port->link_nr != link_nr) {
/*
* If out port is not bonded follow
* link_nr.
*/
out_port = out_port->dual_link_port;
}
}
if (i == num_hops - 1)
ret = tb_port_alloc_out_hopid(out_port, dst_hopid,
dst_hopid);
else
ret = tb_port_alloc_out_hopid(out_port, -1, -1);
if (ret < 0)
goto err;
out_hopid = ret;
path->hops[i].in_hop_index = in_hopid;
path->hops[i].in_port = in_port;
path->hops[i].in_counter_index = -1;
path->hops[i].out_port = out_port;
path->hops[i].next_hop_index = out_hopid;
in_hopid = out_hopid;
}
path->tb = tb;
path->name = name;
return path;
err:
tb_path_free(path);
return NULL;
}
/**
* tb_path_free() - free a path
* @path: Path to free
*
* Frees a path. The path does not need to be deactivated.
*/
void tb_path_free(struct tb_path *path)
{
if (path->alloc_hopid) {
int i;
for (i = 0; i < path->path_length; i++) {
const struct tb_path_hop *hop = &path->hops[i];
if (hop->in_port)
tb_port_release_in_hopid(hop->in_port,
hop->in_hop_index);
if (hop->out_port)
tb_port_release_out_hopid(hop->out_port,
hop->next_hop_index);
}
}
kfree(path);
}
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->hops[i].nfc_credits);
if (res)
tb_port_warn(path->hops[i].in_port,
"nfc credits deallocation failed for hop %d\n",
i);
}
}
static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
bool clear_fc)
{
struct tb_regs_hop hop;
ktime_t timeout;
int ret;
/* Disable the path */
ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
if (ret)
return ret;
/* Already disabled */
if (!hop.enable)
return 0;
hop.enable = 0;
ret = tb_port_write(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
if (ret)
return ret;
/* Wait until it is drained */
timeout = ktime_add_ms(ktime_get(), 500);
do {
ret = tb_port_read(port, &hop, TB_CFG_HOPS, 2 * hop_index, 2);
if (ret)
return ret;
if (!hop.pending) {
if (clear_fc) {
/*
* Clear flow control. Protocol adapters
* IFC and ISE bits are vendor defined
* in the USB4 spec so we clear them
* only for pre-USB4 adapters.
*/
if (tb_port_is_null(port) ||
!tb_switch_is_usb4(port->sw)) {
hop.ingress_fc = 0;
hop.ingress_shared_buffer = 0;
}
hop.egress_fc = 0;
hop.egress_shared_buffer = 0;
return tb_port_write(port, &hop, TB_CFG_HOPS,
2 * hop_index, 2);
}
return 0;
}
usleep_range(10, 20);
} while (ktime_before(ktime_get(), timeout));
return -ETIMEDOUT;
}
/**
* tb_path_deactivate_hop() - Deactivate one path in path config space
* @port: Lane or protocol adapter
* @hop_index: HopID of the path to be cleared
*
* This deactivates or clears a single path config space entry at
* @hop_index.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_path_deactivate_hop(struct tb_port *port, int hop_index)
{
return __tb_path_deactivate_hop(port, hop_index, true);
}
static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop)
{
int i, res;
for (i = first_hop; i < path->path_length; i++) {
res = __tb_path_deactivate_hop(path->hops[i].in_port,
path->hops[i].in_hop_index,
path->clear_fc);
if (res && res != -ENODEV)
tb_port_warn(path->hops[i].in_port,
"hop deactivation failed for hop %d, index %d\n",
i, path->hops[i].in_hop_index);
}
}
void tb_path_deactivate(struct tb_path *path)
{
if (!path->activated) {
tb_WARN(path->tb, "trying to deactivate an inactive path\n");
return;
}
tb_dbg(path->tb,
"deactivating %s path from %llx:%u to %llx:%u\n",
path->name, tb_route(path->hops[0].in_port->sw),
path->hops[0].in_port->port,
tb_route(path->hops[path->path_length - 1].out_port->sw),
path->hops[path->path_length - 1].out_port->port);
__tb_path_deactivate_hops(path, 0);
__tb_path_deallocate_nfc(path, 0);
path->activated = false;
}
/**
* tb_path_activate() - activate a path
* @path: Path to activate
*
* Activate a path starting with the first hop and ending on the last hop. The
* caller must fill path->hops before calling tb_path_activate().
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_path_activate(struct tb_path *path)
{
int i, res;
enum tb_path_port out_mask, in_mask;
if (path->activated) {
tb_WARN(path->tb, "trying to activate already activated path\n");
return -EINVAL;
}
tb_dbg(path->tb,
"activating %s path from %llx:%u to %llx:%u\n",
path->name, tb_route(path->hops[0].in_port->sw),
path->hops[0].in_port->port,
tb_route(path->hops[path->path_length - 1].out_port->sw),
path->hops[path->path_length - 1].out_port->port);
/* Clear counters. */
for (i = path->path_length - 1; i >= 0; i--) {
if (path->hops[i].in_counter_index == -1)
continue;
res = tb_port_clear_counter(path->hops[i].in_port,
path->hops[i].in_counter_index);
if (res)
goto err;
}
/* 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->hops[i].nfc_credits);
if (res) {
__tb_path_deallocate_nfc(path, i);
goto err;
}
}
/* Activate hops. */
for (i = 0; i < path->path_length; i++) {
struct tb_regs_hop hop = { 0 };
/* If it is left active deactivate it first */
__tb_path_deactivate_hop(path->hops[i].in_port,
path->hops[i].in_hop_index, path->clear_fc);
/* Needed for USB4 routers, read path config space before write */
res = tb_port_read(path->hops[i].in_port, &hop, TB_CFG_HOPS,
2 * path->hops[i].in_hop_index, 2);
if (res)
goto err;
hop.next_hop = path->hops[i].next_hop_index;
hop.out_port = path->hops[i].out_port->port;
hop.pmps = path->hops[i].pm_support;
hop.unknown1 = 0;
hop.enable = 1;
out_mask = (i == path->path_length - 1) ?
TB_PATH_DESTINATION : TB_PATH_INTERNAL;
in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL;
hop.weight = path->weight;
hop.unknown2 = 0;
hop.priority = path->priority;
hop.drop_packages = path->drop_packages;
hop.counter = path->hops[i].in_counter_index;
hop.counter_enable = path->hops[i].in_counter_index != -1;
hop.egress_fc = path->egress_fc_enable & out_mask;
hop.egress_shared_buffer = path->egress_shared_buffer & out_mask;
/*
* Protocol adapters IFC and ISE bits, and Path Credits
* Allocated are vendor defined in the USB4 spec so we
* program them only for pre-USB4 and lane adapters.
*/
if (tb_port_is_null(path->hops[i].in_port) ||
!tb_switch_is_usb4(path->hops[i].in_port->sw)) {
hop.initial_credits = path->hops[i].initial_credits;
hop.ingress_fc = path->ingress_fc_enable & in_mask;
hop.ingress_shared_buffer =
path->ingress_shared_buffer & in_mask;
}
hop.unknown3 = 0;
tb_port_dbg(path->hops[i].in_port, "Writing hop %d\n", i);
tb_dump_hop(&path->hops[i], &hop);
res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS,
2 * path->hops[i].in_hop_index, 2);
if (res) {
__tb_path_deactivate_hops(path, 0);
__tb_path_deallocate_nfc(path, 0);
goto err;
}
}
path->activated = true;
tb_dbg(path->tb, "%s path activation complete\n", path->name);
return 0;
err:
tb_warn(path->tb, "%s path activation failed: %d\n", path->name, res);
return res;
}
/**
* tb_path_is_invalid() - check whether any ports on the path are invalid
* @path: Path to check
*
* Return: %true if the path is invalid, %false otherwise.
*/
bool tb_path_is_invalid(struct tb_path *path)
{
int i = 0;
for (i = 0; i < path->path_length; i++) {
if (path->hops[i].in_port->sw->is_unplugged)
return true;
if (path->hops[i].out_port->sw->is_unplugged)
return true;
}
return false;
}
/**
* tb_path_port_on_path() - Does the path go through certain port
* @path: Path to check
* @port: Switch to check
*
* Goes over all hops on path and checks if @port is any of them.
* Direction does not matter.
*
* Return: %true if port is on the path, %false otherwise.
*/
bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port)
{
int i;
for (i = 0; i < path->path_length; i++) {
if (path->hops[i].in_port == port ||
path->hops[i].out_port == port)
return true;
}
return false;
}
+622
View File
@@ -0,0 +1,622 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Thunderbolt driver - PCI NHI driver
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/property.h>
#include <linux/string_helpers.h>
#include <linux/suspend.h>
#include "nhi.h"
#include "nhi_regs.h"
#include "tb.h"
/**
* struct tb_nhi_pci - NHI device connected over PCIe
* @nhi: NHI device
* @msix_ida: Used to allocate MSI-X vectors for rings
*/
struct tb_nhi_pci {
struct tb_nhi nhi;
struct ida msix_ida;
};
static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi)
{
return container_of(nhi, struct tb_nhi_pci, nhi);
}
static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci)
{
struct tb_nhi *nhi = &nhi_pci->nhi;
struct pci_dev *pdev = to_pci_dev(nhi->dev);
if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
/*
* Intel hardware supports auto clear of the interrupt
* status register right after interrupt is being
* issued.
*/
nhi->quirks |= QUIRK_AUTO_CLEAR_INT;
switch (pdev->device) {
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
/*
* Falcon Ridge controller needs the end-to-end
* flow control workaround to avoid losing Rx
* packets when RING_FLAG_E2E is set.
*/
nhi->quirks |= QUIRK_E2E;
break;
}
}
}
static int nhi_pci_check_iommu_pdev(struct pci_dev *pdev, void *data)
{
if (!pdev->external_facing ||
!device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION))
return 0;
*(bool *)data = true;
return 1; /* Stop walking */
}
static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci)
{
struct tb_nhi *nhi = &nhi_pci->nhi;
struct pci_dev *pdev = to_pci_dev(nhi->dev);
struct pci_bus *bus = pdev->bus;
bool port_ok = false;
/*
* Ideally what we'd do here is grab every PCI device that
* represents a tunnelling adapter for this NHI and check their
* status directly, but unfortunately USB4 seems to make it
* obnoxiously difficult to reliably make any correlation.
*
* So for now we'll have to bodge it... Hoping that the system
* is at least sane enough that an adapter is in the same PCI
* segment as its NHI, if we can find *something* on that segment
* which meets the requirements for Kernel DMA Protection, we'll
* take that to imply that firmware is aware and has (hopefully)
* done the right thing in general. We need to know that the PCI
* layer has seen the ExternalFacingPort property which will then
* inform the IOMMU layer to enforce the complete "untrusted DMA"
* flow, but also that the IOMMU driver itself can be trusted not
* to have been subverted by a pre-boot DMA attack.
*/
while (bus->parent)
bus = bus->parent;
pci_walk_bus(bus, nhi_pci_check_iommu_pdev, &port_ok);
nhi->iommu_dma_protection = port_ok;
dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n",
str_enabled_disabled(port_ok));
}
static int nhi_pci_init_msi(struct tb_nhi *nhi)
{
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
struct pci_dev *pdev = to_pci_dev(nhi->dev);
struct device *dev = &pdev->dev;
int res, irq, nvec;
ida_init(&nhi_pci->msix_ida);
/*
* The NHI has 16 MSI-X vectors or a single MSI. We first try to
* get all MSI-X vectors and if we succeed, each ring will have
* one MSI-X. If for some reason that does not work out, we
* fallback to a single MSI.
*/
nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS,
PCI_IRQ_MSIX);
if (nvec < 0) {
nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (nvec < 0)
return nvec;
INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work);
irq = pci_irq_vector(pdev, 0);
if (irq < 0)
return irq;
res = devm_request_irq(&pdev->dev, irq, nhi_msi,
IRQF_NO_SUSPEND, "thunderbolt", nhi);
if (res)
return dev_err_probe(dev, res, "request_irq failed, aborting\n");
}
return 0;
}
static bool nhi_pci_imr_valid(struct pci_dev *pdev)
{
u8 val;
if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val))
return !!val;
return true;
}
static void nhi_pci_start_dma_port(struct tb_nhi *nhi)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
struct pci_dev *root_port;
/*
* During host router NVM upgrade we should not allow root port to
* go into D3cold because some root ports cannot trigger PME
* itself. To be on the safe side keep the root port in D0 during
* the whole upgrade process.
*/
root_port = pcie_find_root_port(pdev);
if (root_port)
pm_runtime_get_noresume(&root_port->dev);
}
static void nhi_pci_complete_dma_port(struct tb_nhi *nhi)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
struct pci_dev *root_port;
root_port = pcie_find_root_port(pdev);
if (root_port)
pm_runtime_put(&root_port->dev);
}
static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend)
{
struct tb_nhi *nhi = ring->nhi;
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
struct pci_dev *pdev = to_pci_dev(nhi->dev);
unsigned long irqflags;
int ret;
if (!pdev->msix_enabled)
return 0;
ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL);
if (ret < 0)
return ret;
ring->vector = ret;
ret = pci_irq_vector(pdev, ring->vector);
if (ret < 0)
goto err_ida_remove;
ring->irq = ret;
irqflags = no_suspend ? IRQF_NO_SUSPEND : 0;
ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring);
if (ret)
goto err_ida_remove;
return 0;
err_ida_remove:
ida_free(&nhi_pci->msix_ida, ring->vector);
return ret;
}
static void nhi_pci_ring_release_msix(struct tb_ring *ring)
{
struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi);
if (ring->irq <= 0)
return;
free_irq(ring->irq, ring);
ida_free(&nhi_pci->msix_ida, ring->vector);
ring->vector = 0;
ring->irq = 0;
}
static void nhi_pci_shutdown(struct tb_nhi *nhi)
{
struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi);
struct pci_dev *pdev = to_pci_dev(nhi->dev);
/*
* We have to release the irq before calling flush_work. Otherwise an
* already executing IRQ handler could call schedule_work again.
*/
if (!pdev->msix_enabled) {
devm_free_irq(nhi->dev, pdev->irq, nhi);
flush_work(&nhi->interrupt_work);
}
ida_destroy(&nhi_pci->msix_ida);
}
static bool nhi_pci_is_present(struct tb_nhi *nhi)
{
return pci_device_is_present(to_pci_dev(nhi->dev));
}
static const struct tb_nhi_ops pci_nhi_default_ops = {
.pre_nvm_auth = nhi_pci_start_dma_port,
.post_nvm_auth = nhi_pci_complete_dma_port,
.request_ring_irq = nhi_pci_ring_request_msix,
.release_ring_irq = nhi_pci_ring_release_msix,
.shutdown = nhi_pci_shutdown,
.is_present = nhi_pci_is_present,
.init_interrupts = nhi_pci_init_msi,
};
/* Ice Lake specific NHI operations */
#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */
static int check_for_device(struct device *dev, void *data)
{
return tb_is_switch(dev);
}
static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
{
struct tb *tb = dev_get_drvdata(nhi->dev);
int ret;
ret = device_for_each_child(&tb->root_switch->dev, NULL,
check_for_device);
return ret > 0;
}
static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
u32 vs_cap;
/*
* The Thunderbolt host controller is present always in Ice Lake
* but the firmware may not be loaded and running (depending
* whether there is device connected and so on). Each time the
* controller is used we need to "Force Power" it first and wait
* for the firmware to indicate it is up and running. This "Force
* Power" is really not about actually powering on/off the
* controller so it is accessible even if "Force Power" is off.
*
* The actual power management happens inside shared ACPI power
* resources using standard ACPI methods.
*/
pci_read_config_dword(pdev, VS_CAP_22, &vs_cap);
if (power) {
vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
vs_cap |= VS_CAP_22_FORCE_POWER;
} else {
vs_cap &= ~VS_CAP_22_FORCE_POWER;
}
pci_write_config_dword(pdev, VS_CAP_22, vs_cap);
if (power) {
unsigned int retries = 350;
u32 val;
/* Wait until the firmware tells it is up and running */
do {
pci_read_config_dword(pdev, VS_CAP_9, &val);
if (val & VS_CAP_9_FW_READY)
return 0;
usleep_range(3000, 3100);
} while (--retries);
return -ETIMEDOUT;
}
return 0;
}
static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
u32 data;
data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID);
}
static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
unsigned long end;
u32 data;
if (!timeout)
goto clear;
end = jiffies + msecs_to_jiffies(timeout);
do {
pci_read_config_dword(pdev, VS_CAP_18, &data);
if (data & VS_CAP_18_DONE)
goto clear;
usleep_range(1000, 1100);
} while (time_before(jiffies, end));
return -ETIMEDOUT;
clear:
/* Clear the valid bit */
pci_write_config_dword(pdev, VS_CAP_19, 0);
return 0;
}
static void icl_nhi_set_ltr(struct tb_nhi *nhi)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
u32 max_ltr, ltr;
pci_read_config_dword(pdev, VS_CAP_16, &max_ltr);
max_ltr &= 0xffff;
/* Program the same value for both snoop and no-snoop */
ltr = max_ltr << 16 | max_ltr;
pci_write_config_dword(pdev, VS_CAP_15, ltr);
}
static int icl_nhi_suspend(struct tb_nhi *nhi)
{
struct tb *tb = dev_get_drvdata(nhi->dev);
int ret;
if (icl_nhi_is_device_connected(nhi))
return 0;
if (tb_switch_is_icm(tb->root_switch)) {
/*
* If there is no device connected we need to perform
* both: a handshake through LC mailbox and force power
* down before entering D3.
*/
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
if (ret)
return ret;
}
return icl_nhi_force_power(nhi, false);
}
static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
{
struct tb *tb = dev_get_drvdata(nhi->dev);
enum icl_lc_mailbox_cmd cmd;
if (!pm_suspend_via_firmware())
return icl_nhi_suspend(nhi);
if (!tb_switch_is_icm(tb->root_switch))
return 0;
cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
icl_nhi_lc_mailbox_cmd(nhi, cmd);
return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
}
static int icl_nhi_resume(struct tb_nhi *nhi)
{
int ret;
ret = icl_nhi_force_power(nhi, true);
if (ret)
return ret;
icl_nhi_set_ltr(nhi);
return 0;
}
static void icl_nhi_shutdown(struct tb_nhi *nhi)
{
nhi_pci_shutdown(nhi);
icl_nhi_force_power(nhi, false);
}
static const struct tb_nhi_ops icl_nhi_ops = {
.init = icl_nhi_resume,
.suspend_noirq = icl_nhi_suspend_noirq,
.resume_noirq = icl_nhi_resume,
.runtime_suspend = icl_nhi_suspend,
.runtime_resume = icl_nhi_resume,
.shutdown = icl_nhi_shutdown,
.pre_nvm_auth = nhi_pci_start_dma_port,
.post_nvm_auth = nhi_pci_complete_dma_port,
.request_ring_irq = nhi_pci_ring_request_msix,
.release_ring_irq = nhi_pci_ring_release_msix,
.is_present = nhi_pci_is_present,
.init_interrupts = nhi_pci_init_msi,
};
static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct device *dev = &pdev->dev;
struct tb_nhi_pci *nhi_pci;
struct tb_nhi *nhi;
int res;
if (!nhi_pci_imr_valid(pdev))
return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n");
res = pcim_enable_device(pdev);
if (res)
return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n");
nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL);
if (!nhi_pci)
return -ENOMEM;
nhi = &nhi_pci->nhi;
nhi->dev = dev;
nhi->ops = (const struct tb_nhi_ops *)id->driver_data ?: &pci_nhi_default_ops;
nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt");
res = PTR_ERR_OR_ZERO(nhi->iobase);
if (res)
return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
nhi_pci_check_quirks(nhi_pci);
nhi_pci_check_iommu(nhi_pci);
pci_set_master(pdev);
return nhi_probe(&nhi_pci->nhi);
}
static void nhi_pci_remove(struct pci_dev *pdev)
{
struct tb *tb = pci_get_drvdata(pdev);
struct tb_nhi *nhi = tb->nhi;
pm_runtime_get_sync(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_forbid(&pdev->dev);
tb_domain_remove(tb);
wait_for_completion(&nhi->domain_released);
nhi_shutdown(nhi);
}
static struct pci_device_id nhi_ids[] = {
/*
* We have to specify class, the TB bridges use the same device and
* vendor (sub)id on gen 1 and gen 2 controllers.
*/
{
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
.vendor = PCI_VENDOR_ID_INTEL,
.device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
.subvendor = 0x2222, .subdevice = 0x1111,
},
{
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
.vendor = PCI_VENDOR_ID_INTEL,
.device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
.subvendor = 0x2222, .subdevice = 0x1111,
},
{
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
.vendor = PCI_VENDOR_ID_INTEL,
.device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI,
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
},
{
.class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0,
.vendor = PCI_VENDOR_ID_INTEL,
.device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI,
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
},
/* Thunderbolt 3 */
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
/* Thunderbolt 4 */
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0),
.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 },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0),
.driver_data = (kernel_ulong_t)&icl_nhi_ops },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) },
{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) },
/* Any USB4 compliant host */
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) },
{ 0,}
};
MODULE_DEVICE_TABLE(pci, nhi_ids);
MODULE_DESCRIPTION("Thunderbolt/USB4 core driver");
MODULE_LICENSE("GPL");
static struct pci_driver nhi_driver = {
.name = "thunderbolt",
.id_table = nhi_ids,
.probe = nhi_pci_probe,
.remove = nhi_pci_remove,
.shutdown = nhi_pci_remove,
.driver.pm = &nhi_pm_ops,
};
static int __init nhi_init(void)
{
int ret;
ret = tb_domain_init();
if (ret)
return ret;
ret = pci_register_driver(&nhi_driver);
if (ret)
tb_domain_exit();
return ret;
}
static void __exit nhi_unload(void)
{
pci_unregister_driver(&nhi_driver);
tb_domain_exit();
}
rootfs_initcall(nhi_init);
module_exit(nhi_unload);
+858
View File
@@ -0,0 +1,858 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt XDomain property support
*
* Copyright (C) 2017, Intel Corporation
* Authors: Michael Jamet <michael.jamet@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/err.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uuid.h>
#include <linux/thunderbolt.h>
struct tb_property_entry {
u32 key_hi;
u32 key_lo;
u16 length;
u8 reserved;
u8 type;
u32 value;
};
struct tb_property_rootdir_entry {
u32 magic;
u32 length;
struct tb_property_entry entries[];
};
struct tb_property_dir_entry {
u32 uuid[4];
struct tb_property_entry entries[];
};
#define TB_PROPERTY_ROOTDIR_MAGIC 0x55584401
#define TB_PROPERTY_MAX_DEPTH 8
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
size_t block_len, unsigned int dir_offset, size_t dir_len,
bool is_root, unsigned int depth);
static struct tb_property *tb_property_copy(const struct tb_property *property);
static inline void parse_dwdata(void *dst, const void *src, size_t dwords)
{
be32_to_cpu_array(dst, src, dwords);
}
static inline void format_dwdata(void *dst, const void *src, size_t dwords)
{
cpu_to_be32_array(dst, src, dwords);
}
static bool tb_property_entry_valid(const struct tb_property_entry *entry,
size_t block_len)
{
u32 end;
switch (entry->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
case TB_PROPERTY_TYPE_DATA:
case TB_PROPERTY_TYPE_TEXT:
if (!entry->length)
return false;
if (entry->length > block_len)
return false;
if (check_add_overflow(entry->value, entry->length, &end) ||
end > block_len)
return false;
break;
case TB_PROPERTY_TYPE_VALUE:
if (entry->length != 1)
return false;
break;
}
return true;
}
static bool tb_property_key_valid(const char *key)
{
return key && strlen(key) <= TB_PROPERTY_KEY_SIZE;
}
static struct tb_property *
tb_property_alloc(const char *key, enum tb_property_type type)
{
struct tb_property *property;
property = kzalloc_obj(*property);
if (!property)
return NULL;
strcpy(property->key, key);
property->type = type;
INIT_LIST_HEAD(&property->list);
return property;
}
static struct tb_property *tb_property_parse(const u32 *block, size_t block_len,
const struct tb_property_entry *entry,
unsigned int depth)
{
char key[TB_PROPERTY_KEY_SIZE + 1];
struct tb_property *property;
struct tb_property_dir *dir;
if (!tb_property_entry_valid(entry, block_len))
return NULL;
parse_dwdata(key, entry, 2);
key[TB_PROPERTY_KEY_SIZE] = '\0';
property = tb_property_alloc(key, entry->type);
if (!property)
return NULL;
property->length = entry->length;
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
dir = __tb_property_parse_dir(block, block_len, entry->value,
entry->length, false, depth + 1);
if (!dir) {
kfree(property);
return NULL;
}
property->value.dir = dir;
break;
case TB_PROPERTY_TYPE_DATA:
property->value.data = kcalloc(property->length, sizeof(u32),
GFP_KERNEL);
if (!property->value.data) {
kfree(property);
return NULL;
}
parse_dwdata(property->value.data, block + entry->value,
entry->length);
break;
case TB_PROPERTY_TYPE_TEXT:
property->value.text = kcalloc(property->length, sizeof(u32),
GFP_KERNEL);
if (!property->value.text) {
kfree(property);
return NULL;
}
parse_dwdata(property->value.text, block + entry->value,
entry->length);
/* Force null termination */
property->value.text[property->length * 4 - 1] = '\0';
break;
case TB_PROPERTY_TYPE_VALUE:
property->value.immediate = entry->value;
break;
default:
property->type = TB_PROPERTY_TYPE_UNKNOWN;
break;
}
return property;
}
static struct tb_property_dir *__tb_property_parse_dir(const u32 *block,
size_t block_len, unsigned int dir_offset, size_t dir_len, bool is_root,
unsigned int depth)
{
const struct tb_property_entry *entries;
size_t i, content_len, nentries;
unsigned int content_offset;
struct tb_property_dir *dir;
if (depth > TB_PROPERTY_MAX_DEPTH)
return NULL;
dir = kzalloc_obj(*dir);
if (!dir)
return NULL;
INIT_LIST_HEAD(&dir->properties);
if (is_root) {
content_offset = dir_offset + 2;
content_len = dir_len;
if (content_offset + content_len > block_len) {
tb_property_free_dir(dir);
return NULL;
}
} else {
if (dir_len < 4) {
tb_property_free_dir(dir);
return NULL;
}
dir->uuid = kmemdup(&block[dir_offset], sizeof(*dir->uuid),
GFP_KERNEL);
if (!dir->uuid) {
tb_property_free_dir(dir);
return NULL;
}
content_offset = dir_offset + 4;
content_len = dir_len - 4; /* Length includes UUID */
}
entries = (const struct tb_property_entry *)&block[content_offset];
nentries = content_len / (sizeof(*entries) / 4);
for (i = 0; i < nentries; i++) {
struct tb_property *property;
property = tb_property_parse(block, block_len, &entries[i], depth);
if (!property) {
tb_property_free_dir(dir);
return NULL;
}
list_add_tail(&property->list, &dir->properties);
}
return dir;
}
/**
* tb_property_parse_dir() - Parses properties from given property block
* @block: Property block to parse
* @block_len: Number of dword elements in the property block
*
* This function parses the XDomain properties data block into format that
* can be traversed using the helper functions provided by this module.
*
* The resulting &struct tb_property_dir needs to be released by
* calling tb_property_free_dir() when not needed anymore.
*
* The @block is expected to be root directory.
*
* Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
*/
struct tb_property_dir *tb_property_parse_dir(const u32 *block,
size_t block_len)
{
const struct tb_property_rootdir_entry *rootdir =
(const struct tb_property_rootdir_entry *)block;
if (rootdir->magic != TB_PROPERTY_ROOTDIR_MAGIC)
return NULL;
if (rootdir->length > block_len)
return NULL;
return __tb_property_parse_dir(block, block_len, 0, rootdir->length,
true, 0);
}
/**
* tb_property_create_dir() - Creates new property directory
* @uuid: UUID used to identify the particular directory
*
* Creates new, empty property directory. If @uuid is %NULL then the
* directory is assumed to be root directory.
*
* Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
*/
struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid)
{
struct tb_property_dir *dir;
dir = kzalloc_obj(*dir);
if (!dir)
return NULL;
INIT_LIST_HEAD(&dir->properties);
if (uuid) {
dir->uuid = kmemdup(uuid, sizeof(*dir->uuid), GFP_KERNEL);
if (!dir->uuid) {
kfree(dir);
return NULL;
}
}
return dir;
}
EXPORT_SYMBOL_GPL(tb_property_create_dir);
static void tb_property_free(struct tb_property *property)
{
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
tb_property_free_dir(property->value.dir);
break;
case TB_PROPERTY_TYPE_DATA:
kfree(property->value.data);
break;
case TB_PROPERTY_TYPE_TEXT:
kfree(property->value.text);
break;
default:
break;
}
kfree(property);
}
/**
* tb_property_free_dir() - Release memory allocated for property directory
* @dir: Directory to release
*
* This will release all the memory the directory occupies including all
* descendants. It is OK to pass %NULL @dir, then the function does
* nothing.
*/
void tb_property_free_dir(struct tb_property_dir *dir)
{
struct tb_property *property, *tmp;
if (!dir)
return;
list_for_each_entry_safe(property, tmp, &dir->properties, list) {
list_del(&property->list);
tb_property_free(property);
}
kfree(dir->uuid);
kfree(dir);
}
EXPORT_SYMBOL_GPL(tb_property_free_dir);
static size_t tb_property_dir_length(const struct tb_property_dir *dir,
bool recurse, size_t *data_len)
{
const struct tb_property *property;
size_t len = 0;
if (dir->uuid)
len += sizeof(*dir->uuid) / 4;
else
len += sizeof(struct tb_property_rootdir_entry) / 4;
list_for_each_entry(property, &dir->properties, list) {
len += sizeof(struct tb_property_entry) / 4;
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
if (recurse) {
len += tb_property_dir_length(
property->value.dir, recurse, data_len);
}
/* Reserve dword padding after each directory */
if (data_len)
*data_len += 1;
break;
case TB_PROPERTY_TYPE_DATA:
case TB_PROPERTY_TYPE_TEXT:
if (data_len)
*data_len += property->length;
break;
default:
break;
}
}
return len;
}
static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir,
u32 *block, unsigned int start_offset, size_t block_len)
{
unsigned int data_offset, dir_end;
const struct tb_property *property;
struct tb_property_entry *entry;
size_t dir_len, data_len = 0;
int ret;
/*
* The structure of property block looks like following. Leaf
* data/text is included right after the directory and each
* directory follows each other (even nested ones).
*
* +----------+ <-- start_offset
* | header | <-- root directory header
* +----------+ ---
* | entry 0 | -^--------------------.
* +----------+ | |
* | entry 1 | -|--------------------|--.
* +----------+ | | |
* | entry 2 | -|-----------------. | |
* +----------+ | | | |
* : : | dir_len | | |
* . . | | | |
* : : | | | |
* +----------+ | | | |
* | entry n | v | | |
* +----------+ <-- data_offset | | |
* | data 0 | <------------------|--' |
* +----------+ | |
* | data 1 | <------------------|-----'
* +----------+ |
* | 00000000 | padding |
* +----------+ <-- dir_end <------'
* | UUID | <-- directory UUID (child directory)
* +----------+
* | entry 0 |
* +----------+
* | entry 1 |
* +----------+
* : :
* . .
* : :
* +----------+
* | entry n |
* +----------+
* | data 0 |
* +----------+
*
* We use dir_end to hold pointer to the end of the directory. It
* will increase as we add directories and each directory should be
* added starting from previous dir_end.
*/
dir_len = tb_property_dir_length(dir, false, &data_len);
data_offset = start_offset + dir_len;
dir_end = start_offset + data_len + dir_len;
if (data_offset > dir_end)
return -EINVAL;
if (dir_end > block_len)
return -EINVAL;
/* Write headers first */
if (dir->uuid) {
struct tb_property_dir_entry *pe;
pe = (struct tb_property_dir_entry *)&block[start_offset];
memcpy(pe->uuid, dir->uuid, sizeof(pe->uuid));
entry = pe->entries;
} else {
struct tb_property_rootdir_entry *re;
re = (struct tb_property_rootdir_entry *)&block[start_offset];
re->magic = TB_PROPERTY_ROOTDIR_MAGIC;
re->length = dir_len - sizeof(*re) / 4;
entry = re->entries;
}
list_for_each_entry(property, &dir->properties, list) {
const struct tb_property_dir *child;
format_dwdata(entry, property->key, 2);
entry->type = property->type;
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
child = property->value.dir;
ret = __tb_property_format_dir(child, block, dir_end,
block_len);
if (ret < 0)
return ret;
entry->length = tb_property_dir_length(child, false,
NULL);
entry->value = dir_end;
dir_end = ret;
break;
case TB_PROPERTY_TYPE_DATA:
format_dwdata(&block[data_offset], property->value.data,
property->length);
entry->length = property->length;
entry->value = data_offset;
data_offset += entry->length;
break;
case TB_PROPERTY_TYPE_TEXT:
format_dwdata(&block[data_offset], property->value.text,
property->length);
entry->length = property->length;
entry->value = data_offset;
data_offset += entry->length;
break;
case TB_PROPERTY_TYPE_VALUE:
entry->length = property->length;
entry->value = property->value.immediate;
break;
default:
break;
}
entry++;
}
return dir_end;
}
/**
* tb_property_format_dir() - Formats directory to the packed XDomain format
* @dir: Directory to format
* @block: Property block where the packed data is placed
* @block_len: Length of the property block
*
* This function formats the directory to the packed format that can be
* then sent over the thunderbolt fabric to receiving host.
*
* Passing %NULL in @block returns number of entries the block takes.
*
* Return: %0 on success, negative errno otherwise.
*/
ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
size_t block_len)
{
ssize_t ret;
if (!block) {
size_t dir_len, data_len = 0;
dir_len = tb_property_dir_length(dir, true, &data_len);
return dir_len + data_len;
}
ret = __tb_property_format_dir(dir, block, 0, block_len);
return ret < 0 ? ret : 0;
}
static struct tb_property_dir *copy_dir(const struct tb_property_dir *dir)
{
struct tb_property *property, *p;
struct tb_property_dir *d;
if (!dir)
return NULL;
d = tb_property_create_dir(dir->uuid);
if (!d)
return NULL;
list_for_each_entry(property, &dir->properties, list) {
p = tb_property_copy(property);
if (!p)
goto err_free;
list_add_tail(&p->list, &d->properties);
}
return d;
err_free:
tb_property_free_dir(d);
return NULL;
}
static struct tb_property *tb_property_copy(const struct tb_property *property)
{
struct tb_property *p;
p = tb_property_alloc(property->key, property->type);
if (!p)
return NULL;
p->length = property->length;
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
p->value.dir = copy_dir(property->value.dir);
if (!p->value.dir)
goto err_free;
break;
case TB_PROPERTY_TYPE_DATA:
p->value.data = kmemdup(property->value.data,
property->length * 4,
GFP_KERNEL);
if (!p->value.data)
goto err_free;
break;
case TB_PROPERTY_TYPE_TEXT:
p->value.text = kzalloc(p->length * 4, GFP_KERNEL);
if (!p->value.text)
goto err_free;
strcpy(p->value.text, property->value.text);
break;
case TB_PROPERTY_TYPE_VALUE:
p->value.immediate = property->value.immediate;
break;
default:
break;
}
return p;
err_free:
kfree(p);
return NULL;
}
/**
* tb_property_copy_dir() - Take a deep copy of directory
* @dir: Directory to copy
*
* The resulting directory needs to be released by calling tb_property_free_dir().
*
* Return: Pointer to &struct tb_property_dir, %NULL in case of failure.
*/
struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
{
return copy_dir(dir);
}
EXPORT_SYMBOL_GPL(tb_property_copy_dir);
/**
* tb_property_merge_dir() - Merges directory into parent
* @parent: Directory to merge @dir
* @dir: Directory that is merged
* @replace: Replace existing entries
*
* This will merge @dir into @parent. Both must have same UUID. The
* properties in @dir will overwrite overlapping properties in @parent
* if @replace is %true. Contents of @dir is copied (so if it is not
* needed afterwards it needs to relesed by calling tb_property_free_dir()).
*/
int tb_property_merge_dir(struct tb_property_dir *parent,
const struct tb_property_dir *dir,
bool replace)
{
const struct tb_property *property;
if (WARN_ON(parent == dir))
return -EINVAL;
if (!uuid_equal(parent->uuid, dir->uuid))
return -EINVAL;
list_for_each_entry(property, &dir->properties, list) {
struct tb_property *p, *tmp;
tmp = tb_property_copy(property);
if (!tmp)
return -ENOMEM;
p = tb_property_find(parent, property->key, property->type);
if (p) {
if (replace) {
/*
* Found existing property in parent so
* replace with the new one.
*/
list_replace(&p->list, &tmp->list);
tb_property_free(p);
} else {
tb_property_free(tmp);
continue;
}
} else {
list_add_tail(&tmp->list, &parent->properties);
}
}
return 0;
}
EXPORT_SYMBOL_GPL(tb_property_merge_dir);
/**
* tb_property_add_immediate() - Add immediate property to directory
* @parent: Directory to add the property
* @key: Key for the property
* @value: Immediate value to store with the property
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
u32 value)
{
struct tb_property *property;
if (!tb_property_key_valid(key))
return -EINVAL;
property = tb_property_alloc(key, TB_PROPERTY_TYPE_VALUE);
if (!property)
return -ENOMEM;
property->length = 1;
property->value.immediate = value;
list_add_tail(&property->list, &parent->properties);
return 0;
}
EXPORT_SYMBOL_GPL(tb_property_add_immediate);
/**
* tb_property_add_data() - Adds arbitrary data property to directory
* @parent: Directory to add the property
* @key: Key for the property
* @buf: Data buffer to add
* @buflen: Number of bytes in the data buffer
*
* Function takes a copy of @buf and adds it to the directory.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_data(struct tb_property_dir *parent, const char *key,
const void *buf, size_t buflen)
{
/* Need to pad to dword boundary */
size_t size = round_up(buflen, 4);
struct tb_property *property;
if (!tb_property_key_valid(key))
return -EINVAL;
property = tb_property_alloc(key, TB_PROPERTY_TYPE_DATA);
if (!property)
return -ENOMEM;
property->length = size / 4;
property->value.data = kzalloc(size, GFP_KERNEL);
if (!property->value.data) {
kfree(property);
return -ENOMEM;
}
memcpy(property->value.data, buf, buflen);
list_add_tail(&property->list, &parent->properties);
return 0;
}
EXPORT_SYMBOL_GPL(tb_property_add_data);
/**
* tb_property_add_text() - Adds string property to directory
* @parent: Directory to add the property
* @key: Key for the property
* @text: String to add
*
* Function takes a copy of @text and adds it to the directory.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_text(struct tb_property_dir *parent, const char *key,
const char *text)
{
/* Need to pad to dword boundary */
size_t size = round_up(strlen(text) + 1, 4);
struct tb_property *property;
if (!tb_property_key_valid(key))
return -EINVAL;
property = tb_property_alloc(key, TB_PROPERTY_TYPE_TEXT);
if (!property)
return -ENOMEM;
property->length = size / 4;
property->value.text = kzalloc(size, GFP_KERNEL);
if (!property->value.text) {
kfree(property);
return -ENOMEM;
}
strcpy(property->value.text, text);
list_add_tail(&property->list, &parent->properties);
return 0;
}
EXPORT_SYMBOL_GPL(tb_property_add_text);
/**
* tb_property_add_dir() - Adds a directory to the parent directory
* @parent: Directory to add the property
* @key: Key for the property
* @dir: Directory to add
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_property_add_dir(struct tb_property_dir *parent, const char *key,
struct tb_property_dir *dir)
{
struct tb_property *property;
if (!tb_property_key_valid(key))
return -EINVAL;
property = tb_property_alloc(key, TB_PROPERTY_TYPE_DIRECTORY);
if (!property)
return -ENOMEM;
property->value.dir = dir;
list_add_tail(&property->list, &parent->properties);
return 0;
}
EXPORT_SYMBOL_GPL(tb_property_add_dir);
/**
* tb_property_remove() - Removes property from a parent directory
* @property: Property to remove
*
* Note memory for @property is released as well so it is not allowed to
* touch the object after call to this function.
*/
void tb_property_remove(struct tb_property *property)
{
list_del(&property->list);
kfree(property);
}
EXPORT_SYMBOL_GPL(tb_property_remove);
/**
* tb_property_find() - Find a property from a directory
* @dir: Directory where the property is searched
* @key: Key to look for
* @type: Type of the property
*
* Finds and returns property from the given directory. Does not
* recurse into sub-directories.
*
* Return: Pointer to &struct tb_property, %NULL if the property was not found.
*/
struct tb_property *tb_property_find(struct tb_property_dir *dir,
const char *key, enum tb_property_type type)
{
struct tb_property *property;
list_for_each_entry(property, &dir->properties, list) {
if (property->type == type && !strcmp(property->key, key))
return property;
}
return NULL;
}
EXPORT_SYMBOL_GPL(tb_property_find);
/**
* tb_property_get_next() - Get next property from directory
* @dir: Directory holding properties
* @prev: Previous property in the directory (%NULL returns the first)
*
* Return: Pointer to &struct tb_property, %NULL if property was not found.
*/
struct tb_property *tb_property_get_next(struct tb_property_dir *dir,
struct tb_property *prev)
{
if (prev) {
if (list_is_last(&prev->list, &dir->properties))
return NULL;
return list_next_entry(prev, list);
}
return list_first_entry_or_null(&dir->properties, struct tb_property,
list);
}
EXPORT_SYMBOL_GPL(tb_property_get_next);
+144
View File
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt driver - quirks
*
* Copyright (c) 2020 Mario Limonciello <mario.limonciello@dell.com>
*/
#include "tb.h"
static void quirk_force_power_link(struct tb_switch *sw)
{
sw->quirks |= QUIRK_FORCE_POWER_LINK_CONTROLLER;
tb_sw_dbg(sw, "forcing power to link controller\n");
}
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);
}
}
static void quirk_clx_disable(struct tb_switch *sw)
{
if (tb_switch_is_titan_ridge(sw) && sw->nvm && sw->nvm->major >= 0x65)
return;
sw->quirks |= QUIRK_NO_CLX;
tb_sw_dbg(sw, "disabling CL states\n");
}
static void quirk_usb3_maximum_bandwidth(struct tb_switch *sw)
{
struct tb_port *port;
if (tb_switch_is_icm(sw))
return;
tb_switch_for_each_port(sw, port) {
if (!tb_port_is_usb3_down(port))
continue;
port->max_bw = 16376;
tb_port_dbg(port, "USB3 maximum bandwidth limited to %u Mb/s\n",
port->max_bw);
}
}
static void quirk_block_rpm_in_redrive(struct tb_switch *sw)
{
sw->quirks |= QUIRK_KEEP_POWER_IN_DP_REDRIVE;
tb_sw_dbg(sw, "preventing runtime PM in DP redrive mode\n");
}
struct tb_quirk {
u16 hw_vendor_id;
u16 hw_device_id;
u16 vendor;
u16 device;
void (*hook)(struct tb_switch *sw);
};
static const struct tb_quirk tb_quirks[] = {
/* Dell WD19TB supports self-authentication on unplug */
{ 0x0000, 0x0000, 0x00d4, 0xb070, quirk_force_power_link },
{ 0x0000, 0x0000, 0x00d4, 0xb071, quirk_force_power_link },
/* Intel Titan Ridge CLx is unstable on early firmware versions */
{ 0x8086, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE, 0x0000, 0x0000,
quirk_clx_disable },
/*
* Intel Goshen Ridge NVM 27 and before report wrong number of
* DP buffers.
*/
{ 0x8087, 0x0b26, 0x0000, 0x0000, quirk_dp_credit_allocation },
/*
* Limit the maximum USB3 bandwidth for the following Intel USB4
* host routers due to a hardware issue.
*/
{ 0x8087, PCI_DEVICE_ID_INTEL_ADL_NHI0, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_ADL_NHI1, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_RPL_NHI0, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_RPL_NHI1, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_MTL_M_NHI0, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_MTL_P_NHI0, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_MTL_P_NHI1, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000,
quirk_usb3_maximum_bandwidth },
/*
* Block Runtime PM in DP redrive mode for Intel Barlow Ridge host
* controllers.
*/
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI, 0x0000, 0x0000,
quirk_block_rpm_in_redrive },
{ 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI, 0x0000, 0x0000,
quirk_block_rpm_in_redrive },
/*
* CLx is not supported on AMD USB4 Yellow Carp and Pink Sardine platforms.
*/
{ 0x0438, 0x0208, 0x0000, 0x0000, quirk_clx_disable },
{ 0x0438, 0x0209, 0x0000, 0x0000, quirk_clx_disable },
{ 0x0438, 0x020a, 0x0000, 0x0000, quirk_clx_disable },
{ 0x0438, 0x020b, 0x0000, 0x0000, quirk_clx_disable },
};
/**
* tb_check_quirks() - Check for quirks to apply
* @sw: Thunderbolt switch
*
* Apply any quirks for the Thunderbolt controller.
*/
void tb_check_quirks(struct tb_switch *sw)
{
int i;
for (i = 0; i < ARRAY_SIZE(tb_quirks); i++) {
const struct tb_quirk *q = &tb_quirks[i];
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;
tb_sw_dbg(sw, "running %ps\n", q->hook);
q->hook(sw);
}
}
+600
View File
@@ -0,0 +1,600 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thunderbolt/USB4 retimer support.
*
* Copyright (C) 2020, Intel Corporation
* Authors: Kranthi Kuntala <kranthi.kuntala@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <linux/sched/signal.h>
#include "sb_regs.h"
#include "tb.h"
#if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING)
#define TB_MAX_RETIMER_INDEX 6
#else
#define TB_MAX_RETIMER_INDEX 2
#endif
/**
* tb_retimer_nvm_read() - Read contents of retimer NVM
* @rt: Retimer device
* @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.
*
* Return: %0 if the read was successful, negative errno in case of failure.
*/
int tb_retimer_nvm_read(struct tb_retimer *rt, unsigned int address, void *buf,
size_t size)
{
return usb4_port_retimer_nvm_read(rt->port, rt->index, address, buf, size);
}
static int 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 = tb_retimer_nvm_read(rt, 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 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;
int ret;
nvm = tb_nvm_alloc(&rt->dev);
if (IS_ERR(nvm)) {
ret = PTR_ERR(nvm) == -EOPNOTSUPP ? 0 : PTR_ERR(nvm);
goto err_nvm;
}
ret = tb_nvm_read_version(nvm);
if (ret)
goto err_nvm;
ret = tb_nvm_add_active(nvm, nvm_read);
if (ret)
goto err_nvm;
if (!rt->no_nvm_upgrade) {
ret = tb_nvm_add_non_active(nvm, nvm_write);
if (ret)
goto err_nvm;
}
rt->nvm = nvm;
dev_dbg(&rt->dev, "NVM version %x.%x\n", nvm->major, nvm->minor);
return 0;
err_nvm:
dev_dbg(&rt->dev, "NVM upgrade disabled\n");
rt->no_nvm_upgrade = true;
if (!IS_ERR(nvm))
tb_nvm_free(nvm);
return ret;
}
static int tb_retimer_nvm_validate_and_write(struct tb_retimer *rt)
{
unsigned int image_size;
const u8 *buf;
int ret;
ret = tb_nvm_validate(rt->nvm);
if (ret)
return ret;
buf = rt->nvm->buf_data_start;
image_size = rt->nvm->buf_data_size;
ret = usb4_port_retimer_nvm_write(rt->port, rt->index, 0, buf,
image_size);
if (ret)
return ret;
rt->nvm->flushed = true;
return 0;
}
static int tb_retimer_nvm_authenticate(struct tb_retimer *rt, bool auth_only)
{
u32 status;
int ret;
if (auth_only) {
ret = usb4_port_retimer_nvm_set_offset(rt->port, rt->index, 0);
if (ret)
return ret;
}
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,
char *buf)
{
struct tb_retimer *rt = tb_to_retimer(dev);
return sysfs_emit(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 = sysfs_emit(buf, "%#x\n", rt->auth_status);
mutex_unlock(&rt->tb->lock);
return ret;
}
static void tb_retimer_nvm_authenticate_status(struct tb_port *port, u32 *status)
{
int i;
tb_port_dbg(port, "reading NVM authentication status of retimers\n");
/*
* 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++) {
if (usb4_port_retimer_nvm_authenticate_status(port, i, &status[i]))
break;
}
}
static void tb_retimer_set_inbound_sbtx(struct tb_port *port)
{
int i;
/*
* When USB4 port is online sideband communications are
* already up.
*/
if (!usb4_port_device_is_offline(port->usb4))
return;
tb_port_dbg(port, "enabling sideband transactions\n");
for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++)
usb4_port_retimer_set_inbound_sbtx(port, i);
}
static void tb_retimer_unset_inbound_sbtx(struct tb_port *port)
{
int i;
/*
* When USB4 port is offline we need to keep the sideband
* communications up to make it possible to communicate with
* the connected retimers.
*/
if (usb4_port_device_is_offline(port->usb4))
return;
tb_port_dbg(port, "disabling sideband transactions\n");
for (i = TB_MAX_RETIMER_INDEX; i >= 1; i--) {
if (usb4_port_retimer_unset_inbound_sbtx(port, i))
break;
}
}
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);
int val, 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 = kstrtoint(buf, 10, &val);
if (ret)
goto exit_unlock;
/* Always clear status */
rt->auth_status = 0;
if (val) {
/*
* When NVM authentication starts the retimer is not
* accessible so calling tb_retimer_unset_inbound_sbtx()
* will fail and therefore we do not call it. Exception
* is when the validation fails or we only write the new
* NVM image without authentication.
*/
tb_retimer_set_inbound_sbtx(rt->port);
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);
}
}
exit_unlock:
if (ret || val == WRITE_ONLY)
tb_retimer_unset_inbound_sbtx(rt->port);
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 = sysfs_emit(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 sysfs_emit(buf, "%#x\n", rt->vendor);
}
static DEVICE_ATTR_RO(vendor);
static umode_t retimer_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
struct device *dev = kobj_to_dev(kobj);
struct tb_retimer *rt = tb_to_retimer(dev);
if (attr == &dev_attr_nvm_authenticate.attr ||
attr == &dev_attr_nvm_version.attr)
return rt->no_nvm_upgrade ? 0 : attr->mode;
return attr->mode;
}
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 = {
.is_visible = retimer_is_visible,
.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);
}
const 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,
bool on_board)
{
struct tb_retimer *rt;
u32 vendor, device;
int ret;
ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, 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_sb_read(port, USB4_SB_TARGET_RETIMER, 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;
}
rt = kzalloc_obj(*rt);
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;
/*
* Only support NVM upgrade for on-board retimers. The retimers
* on the other side of the connection.
*/
if (!on_board || usb4_port_retimer_nvm_sector_size(port, index) <= 0)
rt->no_nvm_upgrade = true;
rt->dev.parent = &port->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),
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_unregister(&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);
tb_retimer_debugfs_init(rt);
return 0;
}
static void tb_retimer_remove(struct tb_retimer *rt)
{
dev_info(&rt->dev, "retimer disconnected\n");
tb_retimer_debugfs_remove(rt);
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, const 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->usb4->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
* @add: If true also registers found retimers
*
* 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.
*
* Return: %0 on success, negative errno otherwise.
*/
int tb_retimer_scan(struct tb_port *port, bool add)
{
u32 status[TB_MAX_RETIMER_INDEX + 1] = {};
int ret, i, max, last_idx = 0;
/*
* Send broadcast RT to make sure retimer indices facing this
* port are set.
*/
ret = usb4_port_enumerate_retimers(port);
if (ret)
return ret;
/*
* Immediately after sending enumerate retimers read the
* authentication status of each retimer.
*/
tb_retimer_nvm_authenticate_status(port, status);
/*
* Enable sideband channel for each retimer. We can do this
* regardless whether there is device connected or not.
*/
tb_retimer_set_inbound_sbtx(port);
for (max = 1, 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;
max = i;
}
ret = 0;
if (!IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING))
max = min(last_idx, max);
/* Add retimers if they do not exist already */
for (i = 1; i <= max; i++) {
struct tb_retimer *rt;
/* Skip cable retimers */
if (usb4_port_retimer_is_cable(port, i))
continue;
rt = tb_port_find_retimer(port, i);
if (rt) {
put_device(&rt->dev);
} else if (add) {
ret = tb_retimer_add(port, i, status[i], i <= last_idx);
if (ret && ret != -EOPNOTSUPP)
break;
}
}
tb_retimer_unset_inbound_sbtx(port);
return ret;
}
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)
{
struct usb4_port *usb4;
usb4 = port->usb4;
if (usb4)
device_for_each_child_reverse(&usb4->dev, port,
remove_retimer);
}
+112
View File
@@ -0,0 +1,112 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* USB4 port sideband registers found on routers and retimers
*
* Copyright (C) 2020, Intel Corporation
* Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
* Rajmohan Mani <rajmohan.mani@intel.com>
*/
#ifndef _SB_REGS
#define _SB_REGS
#define USB4_SB_VENDOR_ID 0x00
#define USB4_SB_PRODUCT_ID 0x01
#define USB4_SB_FW_VERSION 0x02
#define USB4_SB_DEBUG_CONF 0x05
#define USB4_SB_DEBUG 0x06
#define USB4_SB_LRD_TUNING 0x07
#define USB4_SB_OPCODE 0x08
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_UNSET_INBOUND_SBTX = 0x50555355, /* "USUP" */
USB4_SB_OPCODE_QUERY_LAST_RETIMER = 0x5453414c, /* "LAST" */
USB4_SB_OPCODE_QUERY_CABLE_RETIMER = 0x524c4243, /* "CBLR" */
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" */
USB4_SB_OPCODE_READ_LANE_MARGINING_CAP = 0x50434452, /* "RDCP" */
USB4_SB_OPCODE_RUN_HW_LANE_MARGINING = 0x474d4852, /* "RHMG" */
USB4_SB_OPCODE_RUN_SW_LANE_MARGINING = 0x474d5352, /* "RSMG" */
USB4_SB_OPCODE_READ_SW_MARGIN_ERR = 0x57534452, /* "RDSW" */
};
#define USB4_SB_METADATA 0x09
#define USB4_SB_METADATA_NVM_AUTH_WRITE_MASK GENMASK(5, 0)
#define USB4_SB_LINK_CONF 0x0c
#define USB4_SB_GEN23_TXFFE 0x0d
#define USB4_SB_GEN4_TXFFE 0x0e
#define USB4_SB_VERSION 0x0f
#define USB4_SB_DATA 0x12
/* USB4_SB_OPCODE_READ_LANE_MARGINING_CAP */
#define USB4_MARGIN_CAP_0_MODES_HW BIT(0)
#define USB4_MARGIN_CAP_0_MODES_SW BIT(1)
#define USB4_MARGIN_CAP_0_ALL_LANES BIT(2)
#define USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK GENMASK(4, 3)
#define USB4_MARGIN_CAP_0_VOLTAGE_MIN 0x0
#define USB4_MARGIN_CAP_0_VOLTAGE_HL 0x1
#define USB4_MARGIN_CAP_0_VOLTAGE_BOTH 0x2
#define USB4_MARGIN_CAP_0_TIME BIT(5)
#define USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK GENMASK(12, 6)
#define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK GENMASK(18, 13)
#define USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT BIT(19)
#define USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK GENMASK(26, 20)
#define USB4_MARGIN_CAP_1_MAX_VOLT_OFS_OPT_MASK GENMASK(7, 0)
#define USB4_MARGIN_CAP_1_TIME_DESTR BIT(8)
#define USB4_MARGIN_CAP_1_TIME_INDP_MASK GENMASK(10, 9)
#define USB4_MARGIN_CAP_1_TIME_MIN 0x0
#define USB4_MARGIN_CAP_1_TIME_LR 0x1
#define USB4_MARGIN_CAP_1_TIME_BOTH 0x2
#define USB4_MARGIN_CAP_1_TIME_STEPS_MASK GENMASK(15, 11)
#define USB4_MARGIN_CAP_1_TIME_OFFSET_MASK GENMASK(20, 16)
#define USB4_MARGIN_CAP_1_MIN_BER_MASK GENMASK(25, 21)
#define USB4_MARGIN_CAP_1_MAX_BER_MASK GENMASK(30, 26)
#define USB4_MARGIN_CAP_2_MODES_HW BIT(0)
#define USB4_MARGIN_CAP_2_MODES_SW BIT(1)
#define USB4_MARGIN_CAP_2_TIME BIT(2)
#define USB4_MARGIN_CAP_2_MAX_VOLTAGE_OFFSET_MASK GENMASK(8, 3)
#define USB4_MARGIN_CAP_2_VOLTAGE_STEPS_MASK GENMASK(15, 9)
#define USB4_MARGIN_CAP_2_VOLTAGE_INDP_MASK GENMASK(17, 16)
#define USB4_MARGIN_CAP_2_VOLTAGE_MIN 0x0
#define USB4_MARGIN_CAP_2_VOLTAGE_BOTH 0x1
#define USB4_MARGIN_CAP_2_TIME_INDP_MASK GENMASK(19, 18)
#define USB4_MARGIN_CAP_2_TIME_MIN 0x0
#define USB4_MARGIN_CAP_2_TIME_BOTH 0x1
/* USB4_SB_OPCODE_RUN_HW_LANE_MARGINING */
#define USB4_MARGIN_HW_TIME BIT(3)
#define USB4_MARGIN_HW_RHU BIT(4)
#define USB4_MARGIN_HW_BER_MASK GENMASK(9, 5)
#define USB4_MARGIN_HW_BER_SHIFT 5
#define USB4_MARGIN_HW_OPT_VOLTAGE BIT(10)
/* Applicable to all margin values */
#define USB4_MARGIN_HW_RES_MARGIN_MASK GENMASK(6, 0)
#define USB4_MARGIN_HW_RES_EXCEEDS BIT(7)
/* Shifts for parsing the lane results */
#define USB4_MARGIN_HW_RES_LANE_SHIFT 16
#define USB4_MARGIN_HW_RES_LL_SHIFT 8
/* USB4_SB_OPCODE_RUN_SW_LANE_MARGINING */
#define USB4_MARGIN_SW_LANES_MASK GENMASK(2, 0)
#define USB4_MARGIN_SW_TIME BIT(3)
#define USB4_MARGIN_SW_RH BIT(4)
#define USB4_MARGIN_SW_OPT_VOLTAGE BIT(5)
#define USB4_MARGIN_SW_VT_MASK GENMASK(12, 6)
#define USB4_MARGIN_SW_COUNTER_MASK GENMASK(14, 13)
#define USB4_MARGIN_SW_UPPER_EYE BIT(15)
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK GENMASK(3, 0)
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK GENMASK(7, 4)
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK GENMASK(11, 8)
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+656
View File
@@ -0,0 +1,656 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt control channel messages
*
* Copyright (C) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2017, Intel Corporation
*/
#ifndef _TB_MSGS
#define _TB_MSGS
#include <linux/types.h>
#include <linux/uuid.h>
enum tb_cfg_space {
TB_CFG_HOPS = 0,
TB_CFG_PORT = 1,
TB_CFG_SWITCH = 2,
TB_CFG_COUNTERS = 3,
};
enum tb_cfg_error {
TB_CFG_ERROR_PORT_NOT_CONNECTED = 0,
TB_CFG_ERROR_LINK_ERROR = 1,
TB_CFG_ERROR_INVALID_CONFIG_SPACE = 2,
TB_CFG_ERROR_NO_SUCH_PORT = 4,
TB_CFG_ERROR_ACK_PLUG_EVENT = 7, /* send as reply to TB_CFG_PKG_EVENT */
TB_CFG_ERROR_LOOP = 8,
TB_CFG_ERROR_HEC_ERROR_DETECTED = 12,
TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13,
TB_CFG_ERROR_LOCK = 15,
TB_CFG_ERROR_DP_BW = 32,
TB_CFG_ERROR_ROP_CMPLT = 33,
TB_CFG_ERROR_POP_CMPLT = 34,
TB_CFG_ERROR_PCIE_WAKE = 35,
TB_CFG_ERROR_DP_CON_CHANGE = 36,
TB_CFG_ERROR_DPTX_DISCOVERY = 37,
TB_CFG_ERROR_LINK_RECOVERY = 38,
TB_CFG_ERROR_ASYM_LINK = 39,
};
/* common header */
struct tb_cfg_header {
u32 route_hi:22;
u32 unknown:10; /* highest order bit is set on replies */
u32 route_lo;
} __packed;
/* additional header for read/write packets */
struct tb_cfg_address {
u32 offset:13; /* in dwords */
u32 length:6; /* in dwords */
u32 port:6;
enum tb_cfg_space space:2;
u32 seq:2; /* sequence number */
u32 zero:3;
} __packed;
/* TB_CFG_PKG_READ, response for TB_CFG_PKG_WRITE */
struct cfg_read_pkg {
struct tb_cfg_header header;
struct tb_cfg_address addr;
} __packed;
/* TB_CFG_PKG_WRITE, response for TB_CFG_PKG_READ */
struct cfg_write_pkg {
struct tb_cfg_header header;
struct tb_cfg_address addr;
u32 data[64]; /* maximum size, tb_cfg_address.length has 6 bits */
} __packed;
/* TB_CFG_PKG_ERROR */
struct cfg_error_pkg {
struct tb_cfg_header header;
enum tb_cfg_error error:8;
u32 port:6;
u32 reserved:16;
u32 pg:2;
} __packed;
struct cfg_ack_pkg {
struct tb_cfg_header header;
};
#define TB_CFG_ERROR_PG_HOT_PLUG 0x2
#define TB_CFG_ERROR_PG_HOT_UNPLUG 0x3
/* TB_CFG_PKG_EVENT */
struct cfg_event_pkg {
struct tb_cfg_header header;
u32 port:6;
u32 zero:25;
bool unplug:1;
} __packed;
/* TB_CFG_PKG_RESET */
struct cfg_reset_pkg {
struct tb_cfg_header header;
} __packed;
/* ICM messages */
enum icm_pkg_code {
ICM_GET_TOPOLOGY = 0x1,
ICM_DRIVER_READY = 0x3,
ICM_APPROVE_DEVICE = 0x4,
ICM_CHALLENGE_DEVICE = 0x5,
ICM_ADD_DEVICE_KEY = 0x6,
ICM_GET_ROUTE = 0xa,
ICM_APPROVE_XDOMAIN = 0x10,
ICM_DISCONNECT_XDOMAIN = 0x11,
ICM_PREBOOT_ACL = 0x18,
ICM_USB4_SWITCH_OP = 0x20,
};
enum icm_event_code {
ICM_EVENT_DEVICE_CONNECTED = 0x3,
ICM_EVENT_DEVICE_DISCONNECTED = 0x4,
ICM_EVENT_XDOMAIN_CONNECTED = 0x6,
ICM_EVENT_XDOMAIN_DISCONNECTED = 0x7,
ICM_EVENT_DP_CONFIG_CHANGED = 0x8,
ICM_EVENT_RTD3_VETO = 0xa,
};
struct icm_pkg_header {
u8 code;
u8 flags;
u8 packet_id;
u8 total_packets;
};
#define ICM_FLAGS_ERROR BIT(0)
#define ICM_FLAGS_NO_KEY BIT(1)
#define ICM_FLAGS_SLEVEL_SHIFT 3
#define ICM_FLAGS_SLEVEL_MASK GENMASK(4, 3)
#define ICM_FLAGS_DUAL_LANE BIT(5)
#define ICM_FLAGS_SPEED_GEN3 BIT(7)
#define ICM_FLAGS_WRITE BIT(7)
struct icm_pkg_driver_ready {
struct icm_pkg_header hdr;
};
/* Falcon Ridge only messages */
struct icm_fr_pkg_driver_ready_response {
struct icm_pkg_header hdr;
u8 romver;
u8 ramver;
u16 security_level;
};
#define ICM_FR_SLEVEL_MASK 0xf
/* Falcon Ridge & Alpine Ridge common messages */
struct icm_fr_pkg_get_topology {
struct icm_pkg_header hdr;
};
#define ICM_GET_TOPOLOGY_PACKETS 14
struct icm_fr_pkg_get_topology_response {
struct icm_pkg_header hdr;
u32 route_lo;
u32 route_hi;
u8 first_data;
u8 second_data;
u8 drom_i2c_address_index;
u8 switch_index;
u32 reserved[2];
u32 ports[16];
u32 port_hop_info[16];
};
#define ICM_SWITCH_USED BIT(0)
#define ICM_SWITCH_UPSTREAM_PORT_MASK GENMASK(7, 1)
#define ICM_SWITCH_UPSTREAM_PORT_SHIFT 1
#define ICM_PORT_TYPE_MASK GENMASK(23, 0)
#define ICM_PORT_INDEX_SHIFT 24
#define ICM_PORT_INDEX_MASK GENMASK(31, 24)
struct icm_fr_event_device_connected {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u8 connection_key;
u8 connection_id;
u16 link_info;
u32 ep_name[55];
};
#define ICM_LINK_INFO_LINK_MASK 0x7
#define ICM_LINK_INFO_DEPTH_SHIFT 4
#define ICM_LINK_INFO_DEPTH_MASK GENMASK(7, 4)
#define ICM_LINK_INFO_APPROVED BIT(8)
#define ICM_LINK_INFO_REJECTED BIT(9)
#define ICM_LINK_INFO_BOOT BIT(10)
struct icm_fr_pkg_approve_device {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u8 connection_key;
u8 connection_id;
u16 reserved;
};
struct icm_fr_event_device_disconnected {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
};
struct icm_fr_event_xdomain_connected {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
uuid_t local_uuid;
u32 local_route_hi;
u32 local_route_lo;
u32 remote_route_hi;
u32 remote_route_lo;
};
struct icm_fr_event_xdomain_disconnected {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
};
struct icm_fr_pkg_add_device_key {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u8 connection_key;
u8 connection_id;
u16 reserved;
u32 key[8];
};
struct icm_fr_pkg_add_device_key_response {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u8 connection_key;
u8 connection_id;
u16 reserved;
};
struct icm_fr_pkg_challenge_device {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u8 connection_key;
u8 connection_id;
u16 reserved;
u32 challenge[8];
};
struct icm_fr_pkg_challenge_device_response {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u8 connection_key;
u8 connection_id;
u16 reserved;
u32 challenge[8];
u32 response[8];
};
struct icm_fr_pkg_approve_xdomain {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
};
struct icm_fr_pkg_approve_xdomain_response {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
};
/* Alpine Ridge only messages */
struct icm_ar_pkg_driver_ready_response {
struct icm_pkg_header hdr;
u8 romver;
u8 ramver;
u16 info;
};
#define ICM_AR_FLAGS_RTD3 BIT(6)
#define ICM_AR_INFO_SLEVEL_MASK GENMASK(3, 0)
#define ICM_AR_INFO_BOOT_ACL_SHIFT 7
#define ICM_AR_INFO_BOOT_ACL_MASK GENMASK(11, 7)
#define ICM_AR_INFO_BOOT_ACL_SUPPORTED BIT(13)
struct icm_ar_pkg_get_route {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
};
struct icm_ar_pkg_get_route_response {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
u32 route_hi;
u32 route_lo;
};
struct icm_ar_boot_acl_entry {
u32 uuid_lo;
u32 uuid_hi;
};
#define ICM_AR_PREBOOT_ACL_ENTRIES 16
struct icm_ar_pkg_preboot_acl {
struct icm_pkg_header hdr;
struct icm_ar_boot_acl_entry acl[ICM_AR_PREBOOT_ACL_ENTRIES];
};
struct icm_ar_pkg_preboot_acl_response {
struct icm_pkg_header hdr;
struct icm_ar_boot_acl_entry acl[ICM_AR_PREBOOT_ACL_ENTRIES];
};
/* Titan Ridge messages */
struct icm_tr_pkg_driver_ready_response {
struct icm_pkg_header hdr;
u16 reserved1;
u16 info;
u32 nvm_version;
u16 device_id;
u16 reserved2;
};
#define ICM_TR_FLAGS_RTD3 BIT(6)
#define ICM_TR_INFO_SLEVEL_MASK GENMASK(2, 0)
#define ICM_TR_INFO_PROTO_VERSION_MASK GENMASK(6, 4)
#define ICM_TR_INFO_PROTO_VERSION_SHIFT 4
#define ICM_TR_INFO_BOOT_ACL_SHIFT 7
#define ICM_TR_INFO_BOOT_ACL_MASK GENMASK(12, 7)
struct icm_tr_event_device_connected {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u32 route_hi;
u32 route_lo;
u8 connection_id;
u8 reserved;
u16 link_info;
u32 ep_name[55];
};
struct icm_tr_event_device_disconnected {
struct icm_pkg_header hdr;
u32 route_hi;
u32 route_lo;
};
struct icm_tr_event_xdomain_connected {
struct icm_pkg_header hdr;
u16 reserved;
u16 link_info;
uuid_t remote_uuid;
uuid_t local_uuid;
u32 local_route_hi;
u32 local_route_lo;
u32 remote_route_hi;
u32 remote_route_lo;
};
struct icm_tr_event_xdomain_disconnected {
struct icm_pkg_header hdr;
u32 route_hi;
u32 route_lo;
uuid_t remote_uuid;
};
struct icm_tr_pkg_approve_device {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u32 route_hi;
u32 route_lo;
u8 connection_id;
u8 reserved1[3];
};
struct icm_tr_pkg_add_device_key {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u32 route_hi;
u32 route_lo;
u8 connection_id;
u8 reserved[3];
u32 key[8];
};
struct icm_tr_pkg_challenge_device {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u32 route_hi;
u32 route_lo;
u8 connection_id;
u8 reserved[3];
u32 challenge[8];
};
struct icm_tr_pkg_approve_xdomain {
struct icm_pkg_header hdr;
u32 route_hi;
u32 route_lo;
uuid_t remote_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
};
struct icm_tr_pkg_disconnect_xdomain {
struct icm_pkg_header hdr;
u8 stage;
u8 reserved[3];
u32 route_hi;
u32 route_lo;
uuid_t remote_uuid;
};
struct icm_tr_pkg_challenge_device_response {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u32 route_hi;
u32 route_lo;
u8 connection_id;
u8 reserved[3];
u32 challenge[8];
u32 response[8];
};
struct icm_tr_pkg_add_device_key_response {
struct icm_pkg_header hdr;
uuid_t ep_uuid;
u32 route_hi;
u32 route_lo;
u8 connection_id;
u8 reserved[3];
};
struct icm_tr_pkg_approve_xdomain_response {
struct icm_pkg_header hdr;
u32 route_hi;
u32 route_lo;
uuid_t remote_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
};
struct icm_tr_pkg_disconnect_xdomain_response {
struct icm_pkg_header hdr;
u8 stage;
u8 reserved[3];
u32 route_hi;
u32 route_lo;
uuid_t remote_uuid;
};
/* Ice Lake messages */
struct icm_icl_event_rtd3_veto {
struct icm_pkg_header hdr;
u32 veto_reason;
};
/* USB4 ICM messages */
struct icm_usb4_switch_op {
struct icm_pkg_header hdr;
u32 route_hi;
u32 route_lo;
u32 metadata;
u16 opcode;
u16 data_len_valid;
u32 data[16];
};
#define ICM_USB4_SWITCH_DATA_LEN_MASK GENMASK(3, 0)
#define ICM_USB4_SWITCH_DATA_VALID BIT(4)
struct icm_usb4_switch_op_response {
struct icm_pkg_header hdr;
u32 route_hi;
u32 route_lo;
u32 metadata;
u16 opcode;
u16 status;
u32 data[16];
};
/* XDomain messages */
struct tb_xdomain_header {
u32 route_hi;
u32 route_lo;
u32 length_sn;
};
#define TB_XDOMAIN_LENGTH_MASK GENMASK(5, 0)
#define TB_XDOMAIN_SN_MASK GENMASK(28, 27)
#define TB_XDOMAIN_SN_SHIFT 27
enum tb_xdp_type {
UUID_REQUEST_OLD = 1,
UUID_RESPONSE = 2,
PROPERTIES_REQUEST,
PROPERTIES_RESPONSE,
PROPERTIES_CHANGED_REQUEST,
PROPERTIES_CHANGED_RESPONSE,
ERROR_RESPONSE,
UUID_REQUEST = 12,
LINK_STATE_STATUS_REQUEST = 15,
LINK_STATE_STATUS_RESPONSE,
LINK_STATE_CHANGE_REQUEST,
LINK_STATE_CHANGE_RESPONSE,
};
struct tb_xdp_header {
struct tb_xdomain_header xd_hdr;
uuid_t uuid;
u32 type;
};
struct tb_xdp_error_response {
struct tb_xdp_header hdr;
u32 error;
};
struct tb_xdp_link_state_status {
struct tb_xdp_header hdr;
};
struct tb_xdp_link_state_status_response {
union {
struct tb_xdp_error_response err;
struct {
struct tb_xdp_header hdr;
u32 status;
u8 slw;
u8 tlw;
u8 sls;
u8 tls;
};
};
};
struct tb_xdp_link_state_change {
struct tb_xdp_header hdr;
u8 tlw;
u8 tls;
u16 reserved;
};
struct tb_xdp_link_state_change_response {
union {
struct tb_xdp_error_response err;
struct {
struct tb_xdp_header hdr;
u32 status;
};
};
};
struct tb_xdp_uuid {
struct tb_xdp_header hdr;
};
struct tb_xdp_uuid_response {
union {
struct tb_xdp_error_response err;
struct {
struct tb_xdp_header hdr;
uuid_t src_uuid;
u32 src_route_hi;
u32 src_route_lo;
};
};
};
struct tb_xdp_properties {
struct tb_xdp_header hdr;
uuid_t src_uuid;
uuid_t dst_uuid;
u16 offset;
u16 reserved;
};
struct tb_xdp_properties_response {
union {
struct tb_xdp_error_response err;
struct {
struct tb_xdp_header hdr;
uuid_t src_uuid;
uuid_t dst_uuid;
u16 offset;
u16 data_length;
u32 generation;
u32 data[];
};
};
};
/*
* Max length of data array single XDomain property response is allowed
* to carry.
*/
#define TB_XDP_PROPERTIES_MAX_DATA_LENGTH \
(((256 - 4 - sizeof(struct tb_xdp_properties_response))) / 4)
/* Maximum size of the total property block in dwords we allow */
#define TB_XDP_PROPERTIES_MAX_LENGTH 500
struct tb_xdp_properties_changed {
struct tb_xdp_header hdr;
uuid_t src_uuid;
};
struct tb_xdp_properties_changed_response {
union {
struct tb_xdp_error_response err;
struct tb_xdp_header hdr;
};
};
enum tb_xdp_error {
ERROR_SUCCESS,
ERROR_UNKNOWN_PACKET,
ERROR_UNKNOWN_DOMAIN,
ERROR_NOT_SUPPORTED,
ERROR_NOT_READY,
};
#endif
+634
View File
@@ -0,0 +1,634 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt driver - Port/Switch config area registers
*
* Every thunderbolt device consists (logically) of a switch with multiple
* ports. Every port contains up to four config regions (HOPS, PORT, SWITCH,
* COUNTERS) which are used to configure the device.
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2018, Intel Corporation
*/
#ifndef _TB_REGS
#define _TB_REGS
#include <linux/types.h>
#define TB_ROUTE_SHIFT 8 /* number of bits in a port entry of a route */
/*
* TODO: should be 63? But we do not know how to receive frames larger than 256
* bytes at the frame level. (header + checksum = 16, 60*4 = 240)
*/
#define TB_MAX_CONFIG_RW_LENGTH 60
enum tb_switch_cap {
TB_SWITCH_CAP_TMU = 0x03,
TB_SWITCH_CAP_VSE = 0x05,
};
enum tb_switch_vse_cap {
TB_VSE_CAP_PLUG_EVENTS = 0x01, /* also EEPROM */
TB_VSE_CAP_TIME2 = 0x03,
TB_VSE_CAP_CP_LP = 0x04,
TB_VSE_CAP_LINK_CONTROLLER = 0x06, /* also IECS */
};
enum tb_port_cap {
TB_PORT_CAP_PHY = 0x01,
TB_PORT_CAP_POWER = 0x02,
TB_PORT_CAP_TIME1 = 0x03,
TB_PORT_CAP_ADAP = 0x04,
TB_PORT_CAP_VSE = 0x05,
TB_PORT_CAP_USB4 = 0x06,
};
enum tb_port_state {
TB_PORT_DISABLED = 0, /* tb_cap_phy.disable == 1 */
TB_PORT_CONNECTING = 1, /* retry */
TB_PORT_UP = 2,
TB_PORT_TX_CL0S = 3,
TB_PORT_RX_CL0S = 4,
TB_PORT_CL1 = 5,
TB_PORT_CL2 = 6,
TB_PORT_UNPLUGGED = 7,
};
/* capability headers */
struct tb_cap_basic {
u8 next;
/* enum tb_cap cap:8; prevent "narrower than values of its type" */
u8 cap; /* if cap == 0x05 then we have a extended capability */
} __packed;
/**
* struct tb_cap_extended_short - Switch extended short capability
* @next: Pointer to the next capability. If @next and @length are zero
* then we have a long cap.
* @cap: Base capability ID (see &enum tb_switch_cap)
* @vsec_id: Vendor specific capability ID (see &enum switch_vse_cap)
* @length: Length of this capability
*/
struct tb_cap_extended_short {
u8 next;
u8 cap;
u8 vsec_id;
u8 length;
} __packed;
/**
* struct tb_cap_extended_long - Switch extended long capability
* @zero1: This field should be zero
* @cap: Base capability ID (see &enum tb_switch_cap)
* @vsec_id: Vendor specific capability ID (see &enum switch_vse_cap)
* @zero2: This field should be zero
* @next: Pointer to the next capability
* @length: Length of this capability
*/
struct tb_cap_extended_long {
u8 zero1;
u8 cap;
u8 vsec_id;
u8 zero2;
u16 next;
u16 length;
} __packed;
/**
* struct tb_cap_any - Structure capable of holding every capability
* @basic: Basic capability
* @extended_short: Vendor specific capability
* @extended_long: Vendor specific extended capability
*/
struct tb_cap_any {
union {
struct tb_cap_basic basic;
struct tb_cap_extended_short extended_short;
struct tb_cap_extended_long extended_long;
};
} __packed;
/* capabilities */
struct tb_cap_link_controller {
struct tb_cap_extended_long cap_header;
u32 count:4; /* number of link controllers */
u32 unknown1:4;
u32 base_offset:8; /*
* offset (into this capability) of the configuration
* area of the first link controller
*/
u32 length:12; /* link controller configuration area length */
u32 unknown2:4; /* TODO check that length is correct */
} __packed;
struct tb_cap_phy {
struct tb_cap_basic cap_header;
u32 unknown1:16;
u32 unknown2:14;
bool disable:1;
u32 unknown3:11;
enum tb_port_state state:4;
u32 unknown4:2;
} __packed;
struct tb_eeprom_ctl {
bool fl_sk:1; /* send pulse to transfer one bit */
bool fl_cs:1; /* set to 0 before access */
bool fl_di:1; /* to eeprom */
bool fl_do:1; /* from eeprom */
bool bit_banging_enable:1; /* set to 1 before access */
bool not_present:1; /* should be 0 */
bool unknown1:1;
bool present:1; /* should be 1 */
u32 unknown2:24;
} __packed;
struct tb_cap_plug_events {
struct tb_cap_extended_short cap_header;
u32 __unknown1:2; /* VSC_CS_1 */
u32 plug_events:5; /* VSC_CS_1 */
u32 __unknown2:25; /* VSC_CS_1 */
u32 vsc_cs_2;
u32 vsc_cs_3;
struct tb_eeprom_ctl eeprom_ctl;
u32 __unknown5[7]; /* VSC_CS_5 -> VSC_CS_11 */
u32 drom_offset; /* VSC_CS_12: 32 bit register, but eeprom addresses are 16 bit */
} __packed;
/* device headers */
/* Present on port 0 in TB_CFG_SWITCH at address zero. */
struct tb_regs_switch_header {
/* DWORD 0 */
u16 vendor_id;
u16 device_id;
/* DWORD 1 */
u32 first_cap_offset:8;
u32 upstream_port_number:6;
u32 max_port_number:6;
u32 depth:3;
u32 __unknown1:1;
u32 revision:8;
/* DWORD 2 */
u32 route_lo;
/* DWORD 3 */
u32 route_hi:31;
bool enabled:1;
/* DWORD 4 */
u32 plug_events_delay:8; /*
* RW, pause between plug events in
* milliseconds.
*/
u32 cmuv:8;
u32 __unknown4:8;
u32 thunderbolt_version:8;
} __packed;
/* Used with the router thunderbolt_version */
#define USB4_VERSION_MAJOR_MASK GENMASK(7, 5)
#define ROUTER_CS_1 0x01
#define ROUTER_CS_3 0x03
#define ROUTER_CS_3_V BIT(31)
#define ROUTER_CS_4 0x04
/* Used with the router cmuv field */
#define ROUTER_CS_4_CMUV_V1 0x10
#define ROUTER_CS_4_CMUV_V2 0x20
#define ROUTER_CS_5 0x05
#define ROUTER_CS_5_SLP BIT(0)
#define ROUTER_CS_5_WOP BIT(1)
#define ROUTER_CS_5_WOU BIT(2)
#define ROUTER_CS_5_WOD BIT(3)
#define ROUTER_CS_5_CNS BIT(23)
#define ROUTER_CS_5_PTO BIT(24)
#define ROUTER_CS_5_UTO BIT(25)
#define ROUTER_CS_5_HCO BIT(26)
#define ROUTER_CS_5_CV BIT(31)
#define ROUTER_CS_6 0x06
#define ROUTER_CS_6_SLPR BIT(0)
#define ROUTER_CS_6_TNS BIT(1)
#define ROUTER_CS_6_WOPS BIT(2)
#define ROUTER_CS_6_WOUS BIT(3)
#define ROUTER_CS_6_HCI BIT(18)
#define ROUTER_CS_6_RR BIT(24)
#define ROUTER_CS_6_CR BIT(25)
#define ROUTER_CS_7 0x07
#define ROUTER_CS_9 0x09
#define ROUTER_CS_25 0x19
#define ROUTER_CS_26 0x1a
#define ROUTER_CS_26_OPCODE_MASK GENMASK(15, 0)
#define ROUTER_CS_26_STATUS_MASK GENMASK(29, 24)
#define ROUTER_CS_26_STATUS_SHIFT 24
#define ROUTER_CS_26_ONS BIT(30)
#define ROUTER_CS_26_OV BIT(31)
/* USB4 router operations opcodes */
enum usb4_switch_op {
USB4_SWITCH_OP_QUERY_DP_RESOURCE = 0x10,
USB4_SWITCH_OP_ALLOC_DP_RESOURCE = 0x11,
USB4_SWITCH_OP_DEALLOC_DP_RESOURCE = 0x12,
USB4_SWITCH_OP_NVM_WRITE = 0x20,
USB4_SWITCH_OP_NVM_AUTH = 0x21,
USB4_SWITCH_OP_NVM_READ = 0x22,
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 */
#define TMU_RTR_CS_0 0x00
#define TMU_RTR_CS_0_FREQ_WIND_MASK GENMASK(26, 16)
#define TMU_RTR_CS_0_TD BIT(27)
#define TMU_RTR_CS_0_UCAP BIT(30)
#define TMU_RTR_CS_1 0x01
#define TMU_RTR_CS_1_LOCAL_TIME_NS_MASK GENMASK(31, 16)
#define TMU_RTR_CS_1_LOCAL_TIME_NS_SHIFT 16
#define TMU_RTR_CS_2 0x02
#define TMU_RTR_CS_3 0x03
#define TMU_RTR_CS_3_LOCAL_TIME_NS_MASK GENMASK(15, 0)
#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK GENMASK(31, 16)
#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT 16
#define TMU_RTR_CS_15 0x0f
#define TMU_RTR_CS_15_FREQ_AVG_MASK GENMASK(5, 0)
#define TMU_RTR_CS_15_DELAY_AVG_MASK GENMASK(11, 6)
#define TMU_RTR_CS_15_OFFSET_AVG_MASK GENMASK(17, 12)
#define TMU_RTR_CS_15_ERROR_AVG_MASK GENMASK(23, 18)
#define TMU_RTR_CS_18 0x12
#define TMU_RTR_CS_18_DELTA_AVG_CONST_MASK GENMASK(23, 16)
#define TMU_RTR_CS_22 0x16
#define TMU_RTR_CS_24 0x18
#define TMU_RTR_CS_25 0x19
enum tb_port_type {
TB_TYPE_INACTIVE = 0x000000,
TB_TYPE_PORT = 0x000001,
TB_TYPE_NHI = 0x000002,
/* TB_TYPE_ETHERNET = 0x020000, lower order bits are not known */
/* TB_TYPE_SATA = 0x080000, lower order bits are not known */
TB_TYPE_DP_HDMI_IN = 0x0e0101,
TB_TYPE_DP_HDMI_OUT = 0x0e0102,
TB_TYPE_PCIE_DOWN = 0x100101,
TB_TYPE_PCIE_UP = 0x100102,
TB_TYPE_USB3_DOWN = 0x200101,
TB_TYPE_USB3_UP = 0x200102,
};
/* Present on every port in TB_CF_PORT at address zero. */
struct tb_regs_port_header {
/* DWORD 0 */
u16 vendor_id;
u16 device_id;
/* DWORD 1 */
u32 first_cap_offset:8;
u32 max_counters:11;
u32 counters_support:1;
u32 __unknown1:4;
u32 revision:8;
/* DWORD 2 */
enum tb_port_type type:24;
u32 thunderbolt_version:8;
/* DWORD 3 */
u32 __unknown2:20;
u32 port_number:6;
u32 __unknown3:6;
/* DWORD 4 */
u32 nfc_credits;
/* DWORD 5 */
u32 max_in_hop_id:11;
u32 max_out_hop_id:11;
u32 __unknown4:10;
/* DWORD 6 */
u32 __unknown5;
/* DWORD 7 */
u32 __unknown6;
} __packed;
/* Basic adapter configuration registers */
#define ADP_CS_4 0x04
#define ADP_CS_4_NFC_BUFFERS_MASK GENMASK(9, 0)
#define ADP_CS_4_TOTAL_BUFFERS_MASK GENMASK(29, 20)
#define ADP_CS_4_TOTAL_BUFFERS_SHIFT 20
#define ADP_CS_4_LCK BIT(31)
#define ADP_CS_5 0x05
#define ADP_CS_5_LCA_MASK GENMASK(28, 22)
#define ADP_CS_5_LCA_SHIFT 22
#define ADP_CS_5_DHP BIT(31)
/* TMU adapter registers */
#define TMU_ADP_CS_3 0x03
#define TMU_ADP_CS_3_UDM BIT(29)
#define TMU_ADP_CS_6 0x06
#define TMU_ADP_CS_6_DTS BIT(1)
#define TMU_ADP_CS_8 0x08
#define TMU_ADP_CS_8_REPL_TIMEOUT_MASK GENMASK(14, 0)
#define TMU_ADP_CS_8_EUDM BIT(15)
#define TMU_ADP_CS_8_REPL_THRESHOLD_MASK GENMASK(25, 16)
#define TMU_ADP_CS_9 0x09
#define TMU_ADP_CS_9_REPL_N_MASK GENMASK(7, 0)
#define TMU_ADP_CS_9_DIRSWITCH_N_MASK GENMASK(15, 8)
#define TMU_ADP_CS_9_ADP_TS_INTERVAL_MASK GENMASK(31, 16)
/* Lane adapter registers */
#define LANE_ADP_CS_0 0x00
#define LANE_ADP_CS_0_SUPPORTED_SPEED_MASK GENMASK(19, 16)
#define LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT 16
#define LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK GENMASK(25, 20)
#define LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT 20
#define LANE_ADP_CS_0_SUPPORTED_WIDTH_DUAL 0x2
#define LANE_ADP_CS_0_CL0S_SUPPORT BIT(26)
#define LANE_ADP_CS_0_CL1_SUPPORT BIT(27)
#define LANE_ADP_CS_0_CL2_SUPPORT BIT(28)
#define LANE_ADP_CS_1 0x01
#define LANE_ADP_CS_1_TARGET_SPEED_MASK GENMASK(3, 0)
#define LANE_ADP_CS_1_TARGET_SPEED_GEN3 0xc
#define LANE_ADP_CS_1_TARGET_WIDTH_MASK GENMASK(5, 4)
#define LANE_ADP_CS_1_TARGET_WIDTH_SHIFT 4
#define LANE_ADP_CS_1_TARGET_WIDTH_SINGLE 0x1
#define LANE_ADP_CS_1_TARGET_WIDTH_DUAL 0x3
#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_MASK GENMASK(7, 6)
#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_TX 0x1
#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_RX 0x2
#define LANE_ADP_CS_1_TARGET_WIDTH_ASYM_DUAL 0x0
#define LANE_ADP_CS_1_CL0S_ENABLE BIT(10)
#define LANE_ADP_CS_1_CL1_ENABLE BIT(11)
#define LANE_ADP_CS_1_CL2_ENABLE BIT(12)
#define LANE_ADP_CS_1_LD BIT(14)
#define LANE_ADP_CS_1_LB BIT(15)
#define LANE_ADP_CS_1_CURRENT_SPEED_MASK GENMASK(19, 16)
#define LANE_ADP_CS_1_CURRENT_SPEED_SHIFT 16
#define LANE_ADP_CS_1_CURRENT_SPEED_GEN2 0x8
#define LANE_ADP_CS_1_CURRENT_SPEED_GEN3 0x4
#define LANE_ADP_CS_1_CURRENT_SPEED_GEN4 0x2
#define LANE_ADP_CS_1_CURRENT_WIDTH_MASK GENMASK(25, 20)
#define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT 20
#define LANE_ADP_CS_1_PMS BIT(30)
/* 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)
#define PORT_CS_18_CPS BIT(10)
#define PORT_CS_18_WOCS BIT(16)
#define PORT_CS_18_WODS BIT(17)
#define PORT_CS_18_WOU4S BIT(18)
#define PORT_CS_18_CSA BIT(22)
#define PORT_CS_18_TIP BIT(24)
#define PORT_CS_19 0x13
#define PORT_CS_19_DPR BIT(0)
#define PORT_CS_19_PC BIT(3)
#define PORT_CS_19_PID BIT(4)
#define PORT_CS_19_WOC BIT(16)
#define PORT_CS_19_WOD BIT(17)
#define PORT_CS_19_WOU4 BIT(18)
#define PORT_CS_19_START_ASYM BIT(24)
/* Display Port adapter registers */
#define ADP_DP_CS_0 0x00
#define ADP_DP_CS_0_VIDEO_HOPID_MASK GENMASK(26, 16)
#define ADP_DP_CS_0_VIDEO_HOPID_SHIFT 16
#define ADP_DP_CS_0_AE BIT(30)
#define ADP_DP_CS_0_VE BIT(31)
#define ADP_DP_CS_1_AUX_TX_HOPID_MASK GENMASK(10, 0)
#define ADP_DP_CS_1_AUX_RX_HOPID_MASK GENMASK(21, 11)
#define ADP_DP_CS_1_AUX_RX_HOPID_SHIFT 11
#define ADP_DP_CS_2 0x02
#define ADP_DP_CS_2_NRD_MLC_MASK GENMASK(2, 0)
#define ADP_DP_CS_2_HPD BIT(6)
#define ADP_DP_CS_2_NRD_MLR_MASK GENMASK(9, 7)
#define ADP_DP_CS_2_NRD_MLR_SHIFT 7
#define ADP_DP_CS_2_CA BIT(10)
#define ADP_DP_CS_2_GR_MASK GENMASK(12, 11)
#define ADP_DP_CS_2_GR_SHIFT 11
#define ADP_DP_CS_2_GR_0_25G 0x0
#define ADP_DP_CS_2_GR_0_5G 0x1
#define ADP_DP_CS_2_GR_1G 0x2
#define ADP_DP_CS_2_GROUP_ID_MASK GENMASK(15, 13)
#define ADP_DP_CS_2_GROUP_ID_SHIFT 13
#define ADP_DP_CS_2_CM_ID_MASK GENMASK(19, 16)
#define ADP_DP_CS_2_CM_ID_SHIFT 16
#define ADP_DP_CS_2_CMMS BIT(20)
#define ADP_DP_CS_2_ESTIMATED_BW_MASK GENMASK(31, 24)
#define ADP_DP_CS_2_ESTIMATED_BW_SHIFT 24
#define ADP_DP_CS_3 0x03
#define ADP_DP_CS_3_HPDC BIT(9)
#define DP_LOCAL_CAP 0x04
#define DP_REMOTE_CAP 0x05
/* For DP IN adapter */
#define DP_STATUS 0x06
#define DP_STATUS_ALLOCATED_BW_MASK GENMASK(31, 24)
#define DP_STATUS_ALLOCATED_BW_SHIFT 24
/* For DP OUT adapter */
#define DP_STATUS_CTRL 0x06
#define DP_STATUS_CTRL_CMHS BIT(25)
#define DP_STATUS_CTRL_UF BIT(26)
#define DP_COMMON_CAP 0x07
/* Only if DP IN supports BW allocation mode */
#define ADP_DP_CS_8 0x08
#define ADP_DP_CS_8_REQUESTED_BW_MASK GENMASK(7, 0)
#define ADP_DP_CS_8_DPME BIT(30)
#define ADP_DP_CS_8_DR BIT(31)
/*
* DP_COMMON_CAP offsets work also for DP_LOCAL_CAP and DP_REMOTE_CAP
* with exception of DPRX done.
*/
#define DP_COMMON_CAP_RATE_MASK GENMASK(11, 8)
#define DP_COMMON_CAP_RATE_SHIFT 8
#define DP_COMMON_CAP_RATE_RBR 0x0
#define DP_COMMON_CAP_RATE_HBR 0x1
#define DP_COMMON_CAP_RATE_HBR2 0x2
#define DP_COMMON_CAP_RATE_HBR3 0x3
#define DP_COMMON_CAP_LANES_MASK GENMASK(14, 12)
#define DP_COMMON_CAP_LANES_SHIFT 12
#define DP_COMMON_CAP_1_LANE 0x0
#define DP_COMMON_CAP_2_LANES 0x1
#define DP_COMMON_CAP_4_LANES 0x2
#define DP_COMMON_CAP_UHBR10 BIT(17)
#define DP_COMMON_CAP_UHBR20 BIT(18)
#define DP_COMMON_CAP_UHBR13_5 BIT(19)
#define DP_COMMON_CAP_LTTPR_NS BIT(27)
#define DP_COMMON_CAP_BW_MODE BIT(28)
#define DP_COMMON_CAP_DPRX_DONE BIT(31)
/* Only present if DP IN supports BW allocation mode */
#define ADP_DP_CS_8 0x08
#define ADP_DP_CS_8_DPME BIT(30)
#define ADP_DP_CS_8_DR BIT(31)
/* PCIe adapter registers */
#define ADP_PCIE_CS_0 0x00
#define ADP_PCIE_CS_0_LTSSM_MASK GENMASK(28, 25)
#define ADP_PCIE_CS_0_PE BIT(31)
#define ADP_PCIE_CS_1 0x01
#define ADP_PCIE_CS_1_EE BIT(0)
enum tb_pcie_ltssm_state {
USB4_PCIE_LTSSM_DETECT,
USB4_PCIE_LTSSM_POLLING,
USB4_PCIE_LTSSM_CONFIG,
USB4_PCIE_LTSSM_CONFIG_IDLE,
USB4_PCIE_LTSSM_RECOVERY,
USB4_PCIE_LTSSM_RECOVERY_IDLE,
USB4_PCIE_LTSSM_L0,
USB4_PCIE_LTSSM_L1,
USB4_PCIE_LTSSM_L2,
USB4_PCIE_LTSSM_DISABLED,
USB4_PCIE_LTSSM_HOT_RESET,
};
/* USB adapter registers */
#define ADP_USB3_CS_0 0x00
#define ADP_USB3_CS_0_V BIT(30)
#define ADP_USB3_CS_0_PE BIT(31)
#define ADP_USB3_CS_1 0x01
#define ADP_USB3_CS_1_CUBW_MASK GENMASK(11, 0)
#define ADP_USB3_CS_1_CDBW_MASK GENMASK(23, 12)
#define ADP_USB3_CS_1_CDBW_SHIFT 12
#define ADP_USB3_CS_1_HCA BIT(31)
#define ADP_USB3_CS_2 0x02
#define ADP_USB3_CS_2_AUBW_MASK GENMASK(11, 0)
#define ADP_USB3_CS_2_ADBW_MASK GENMASK(23, 12)
#define ADP_USB3_CS_2_ADBW_SHIFT 12
#define ADP_USB3_CS_2_CMR BIT(31)
#define ADP_USB3_CS_3 0x03
#define ADP_USB3_CS_3_SCALE_MASK GENMASK(5, 0)
#define ADP_USB3_CS_4 0x04
#define ADP_USB3_CS_4_MSLR_MASK GENMASK(18, 12)
#define ADP_USB3_CS_4_MSLR_SHIFT 12
#define ADP_USB3_CS_4_MSLR_20G 0x1
/* Hop register from TB_CFG_HOPS. 8 byte per entry. */
struct tb_regs_hop {
/* DWORD 0 */
u32 next_hop:11; /*
* hop to take after sending the packet through
* out_port (on the incoming port of the next switch)
*/
u32 out_port:6; /* next port of the path (on the same switch) */
u32 initial_credits:7;
u32 pmps:1;
u32 unknown1:6; /* set to zero */
bool enable:1;
/* DWORD 1 */
u32 weight:4;
u32 unknown2:4; /* set to zero */
u32 priority:3;
bool drop_packages:1;
u32 counter:11; /* index into TB_CFG_COUNTERS on this port */
bool counter_enable:1;
bool ingress_fc:1;
bool egress_fc:1;
bool ingress_shared_buffer:1;
bool egress_shared_buffer:1;
bool pending:1;
u32 unknown3:3; /* set to zero */
} __packed;
/* TMU Thunderbolt 3 registers */
#define TB_TIME_VSEC_3_CS_9 0x9
#define TB_TIME_VSEC_3_CS_9_TMU_OBJ_MASK GENMASK(17, 16)
#define TB_TIME_VSEC_3_CS_26 0x1a
#define TB_TIME_VSEC_3_CS_26_TD BIT(22)
/*
* Used for Titan Ridge only. Bits are part of the same register: TMU_ADP_CS_6
* (see above) as in USB4 spec, but these specific bits are used for Titan Ridge
* only and are reserved in USB4 spec.
*/
#define TMU_ADP_CS_6_DISABLE_TMU_OBJ_MASK GENMASK(3, 2)
#define TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL1 BIT(2)
#define TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL2 BIT(3)
/* Plug Events registers */
#define TB_PLUG_EVENTS_USB_DISABLE BIT(2)
#define TB_PLUG_EVENTS_CS_1_LANE_DISABLE BIT(3)
#define TB_PLUG_EVENTS_CS_1_DPOUT_DISABLE BIT(4)
#define TB_PLUG_EVENTS_CS_1_LOW_DPIN_DISABLE BIT(5)
#define TB_PLUG_EVENTS_CS_1_HIGH_DPIN_DISABLE BIT(6)
#define TB_PLUG_EVENTS_PCIE_WR_DATA 0x1b
#define TB_PLUG_EVENTS_PCIE_CMD 0x1c
#define TB_PLUG_EVENTS_PCIE_CMD_DW_OFFSET_MASK GENMASK(9, 0)
#define TB_PLUG_EVENTS_PCIE_CMD_BR_SHIFT 10
#define TB_PLUG_EVENTS_PCIE_CMD_BR_MASK GENMASK(17, 10)
#define TB_PLUG_EVENTS_PCIE_CMD_RD_WR_MASK BIT(21)
#define TB_PLUG_EVENTS_PCIE_CMD_WR 0x1
#define TB_PLUG_EVENTS_PCIE_CMD_COMMAND_SHIFT 22
#define TB_PLUG_EVENTS_PCIE_CMD_COMMAND_MASK GENMASK(24, 22)
#define TB_PLUG_EVENTS_PCIE_CMD_COMMAND_VAL 0x2
#define TB_PLUG_EVENTS_PCIE_CMD_REQ_ACK_MASK BIT(30)
#define TB_PLUG_EVENTS_PCIE_CMD_TIMEOUT_MASK BIT(31)
#define TB_PLUG_EVENTS_PCIE_CMD_RD_DATA 0x1d
/* CP Low Power registers */
#define TB_LOW_PWR_C1_CL1 0x1
#define TB_LOW_PWR_C1_CL1_OBJ_MASK GENMASK(4, 1)
#define TB_LOW_PWR_C1_CL2_OBJ_MASK GENMASK(4, 1)
#define TB_LOW_PWR_C1_PORT_A_MASK GENMASK(2, 1)
#define TB_LOW_PWR_C0_PORT_B_MASK GENMASK(4, 3)
#define TB_LOW_PWR_C3_CL1 0x3
/* Common link controller registers */
#define TB_LC_DESC 0x02
#define TB_LC_DESC_NLC_MASK GENMASK(3, 0)
#define TB_LC_DESC_SIZE_SHIFT 8
#define TB_LC_DESC_SIZE_MASK GENMASK(15, 8)
#define TB_LC_DESC_PORT_SIZE_SHIFT 16
#define TB_LC_DESC_PORT_SIZE_MASK GENMASK(27, 16)
#define TB_LC_FUSE 0x03
#define TB_LC_SNK_ALLOCATION 0x10
#define TB_LC_SNK_ALLOCATION_SNK0_MASK GENMASK(3, 0)
#define TB_LC_SNK_ALLOCATION_SNK0_CM 0x1
#define TB_LC_SNK_ALLOCATION_SNK1_SHIFT 4
#define TB_LC_SNK_ALLOCATION_SNK1_MASK GENMASK(7, 4)
#define TB_LC_SNK_ALLOCATION_SNK1_CM 0x1
#define TB_LC_POWER 0x740
/* Link controller registers */
#define TB_LC_PORT_MODE 0x26
#define TB_LC_PORT_MODE_DPR BIT(0)
#define TB_LC_CS_42 0x2a
#define TB_LC_CS_42_USB_PLUGGED BIT(31)
#define TB_LC_PORT_ATTR 0x8d
#define TB_LC_PORT_ATTR_BE BIT(12)
#define TB_LC_SX_CTRL 0x96
#define TB_LC_SX_CTRL_WOC BIT(1)
#define TB_LC_SX_CTRL_WOD BIT(2)
#define TB_LC_SX_CTRL_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)
#define TB_LC_SX_CTRL_L1D BIT(17)
#define TB_LC_SX_CTRL_L2C BIT(20)
#define TB_LC_SX_CTRL_L2D BIT(21)
#define TB_LC_SX_CTRL_SLI BIT(29)
#define TB_LC_SX_CTRL_UPSTREAM BIT(30)
#define TB_LC_SX_CTRL_SLP BIT(31)
#define TB_LC_LINK_ATTR 0x97
#define TB_LC_LINK_ATTR_CPS BIT(18)
#define TB_LC_LINK_REQ 0xad
#define TB_LC_LINK_REQ_XHCI_CONNECT BIT(31)
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt tracing support
*
* Copyright (C) 2024, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
* Gil Fine <gil.fine@intel.com>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM thunderbolt
#if !defined(TB_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define TB_TRACE_H_
#include <linux/trace_seq.h>
#include <linux/tracepoint.h>
#include "tb_msgs.h"
#define tb_cfg_type_name(type) { type, #type }
#define show_type_name(val) \
__print_symbolic(val, \
tb_cfg_type_name(TB_CFG_PKG_READ), \
tb_cfg_type_name(TB_CFG_PKG_WRITE), \
tb_cfg_type_name(TB_CFG_PKG_ERROR), \
tb_cfg_type_name(TB_CFG_PKG_NOTIFY_ACK), \
tb_cfg_type_name(TB_CFG_PKG_EVENT), \
tb_cfg_type_name(TB_CFG_PKG_XDOMAIN_REQ), \
tb_cfg_type_name(TB_CFG_PKG_XDOMAIN_RESP), \
tb_cfg_type_name(TB_CFG_PKG_OVERRIDE), \
tb_cfg_type_name(TB_CFG_PKG_RESET), \
tb_cfg_type_name(TB_CFG_PKG_ICM_EVENT), \
tb_cfg_type_name(TB_CFG_PKG_ICM_CMD), \
tb_cfg_type_name(TB_CFG_PKG_ICM_RESP))
#ifndef TB_TRACE_HELPERS
#define TB_TRACE_HELPERS
static inline const char *show_data_read_write(struct trace_seq *p,
const u32 *data)
{
const struct cfg_read_pkg *msg = (const struct cfg_read_pkg *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "offset=%#x, len=%u, port=%d, config=%#x, seq=%d, ",
msg->addr.offset, msg->addr.length, msg->addr.port,
msg->addr.space, msg->addr.seq);
return ret;
}
static inline const char *show_data_error(struct trace_seq *p, const u32 *data)
{
const struct cfg_error_pkg *msg = (const struct cfg_error_pkg *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "error=%#x, port=%d, plug=%#x, ", msg->error,
msg->port, msg->pg);
return ret;
}
static inline const char *show_data_event(struct trace_seq *p, const u32 *data)
{
const struct cfg_event_pkg *msg = (const struct cfg_event_pkg *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "port=%d, unplug=%#x, ", msg->port, msg->unplug);
return ret;
}
static inline const char *show_route(struct trace_seq *p, const u32 *data)
{
const struct tb_cfg_header *header = (const struct tb_cfg_header *)data;
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "route=%llx, ", tb_cfg_get_route(header));
return ret;
}
static inline const char *show_data(struct trace_seq *p, u8 type,
const u32 *data, u32 length)
{
const char *ret = trace_seq_buffer_ptr(p);
const char *prefix = "";
int i;
switch (type) {
case TB_CFG_PKG_READ:
case TB_CFG_PKG_WRITE:
show_route(p, data);
show_data_read_write(p, data);
break;
case TB_CFG_PKG_ERROR:
show_route(p, data);
show_data_error(p, data);
break;
case TB_CFG_PKG_EVENT:
show_route(p, data);
show_data_event(p, data);
break;
case TB_CFG_PKG_ICM_EVENT:
case TB_CFG_PKG_ICM_CMD:
case TB_CFG_PKG_ICM_RESP:
/* ICM messages always target the host router */
trace_seq_puts(p, "route=0, ");
break;
default:
show_route(p, data);
break;
}
trace_seq_printf(p, "data=[");
for (i = 0; i < length; i++) {
trace_seq_printf(p, "%s0x%08x", prefix, data[i]);
prefix = ", ";
}
trace_seq_printf(p, "]");
trace_seq_putc(p, 0);
return ret;
}
#endif
DECLARE_EVENT_CLASS(tb_raw,
TP_PROTO(int index, u8 type, const void *data, size_t size),
TP_ARGS(index, type, data, size),
TP_STRUCT__entry(
__field(int, index)
__field(u8, type)
__field(size_t, size)
__dynamic_array(u32, data, size / 4)
),
TP_fast_assign(
__entry->index = index;
__entry->type = type;
__entry->size = size / 4;
memcpy(__get_dynamic_array(data), data, size);
),
TP_printk("type=%s, size=%zd, domain=%d, %s",
show_type_name(__entry->type), __entry->size, __entry->index,
show_data(p, __entry->type, __get_dynamic_array(data),
__entry->size)
)
);
DEFINE_EVENT(tb_raw, tb_tx,
TP_PROTO(int index, u8 type, const void *data, size_t size),
TP_ARGS(index, type, data, size)
);
DEFINE_EVENT(tb_raw, tb_event,
TP_PROTO(int index, u8 type, const void *data, size_t size),
TP_ARGS(index, type, data, size)
);
TRACE_EVENT(tb_rx,
TP_PROTO(int index, u8 type, const void *data, size_t size, bool dropped),
TP_ARGS(index, type, data, size, dropped),
TP_STRUCT__entry(
__field(int, index)
__field(u8, type)
__field(size_t, size)
__dynamic_array(u32, data, size / 4)
__field(bool, dropped)
),
TP_fast_assign(
__entry->index = index;
__entry->type = type;
__entry->size = size / 4;
memcpy(__get_dynamic_array(data), data, size);
__entry->dropped = dropped;
),
TP_printk("type=%s, dropped=%u, size=%zd, domain=%d, %s",
show_type_name(__entry->type), __entry->dropped,
__entry->size, __entry->index,
show_data(p, __entry->type, __get_dynamic_array(data),
__entry->size)
)
);
#endif /* TB_TRACE_H_ */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Thunderbolt driver - Tunneling support
*
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
* Copyright (C) 2019, Intel Corporation
*/
#ifndef TB_TUNNEL_H_
#define TB_TUNNEL_H_
#include "tb.h"
enum tb_tunnel_type {
TB_TUNNEL_PCI,
TB_TUNNEL_DP,
TB_TUNNEL_DMA,
TB_TUNNEL_USB3,
};
/**
* enum tb_tunnel_state - State of a tunnel
* @TB_TUNNEL_INACTIVE: tb_tunnel_activate() is not called for the tunnel
* @TB_TUNNEL_ACTIVATING: tb_tunnel_activate() returned successfully for the tunnel
* @TB_TUNNEL_ACTIVE: The tunnel is fully active
*/
enum tb_tunnel_state {
TB_TUNNEL_INACTIVE,
TB_TUNNEL_ACTIVATING,
TB_TUNNEL_ACTIVE,
};
/**
* struct tb_tunnel - Tunnel between two ports
* @kref: Reference count
* @tb: Pointer to the domain
* @src_port: Source port of the tunnel
* @dst_port: Destination port of the tunnel. For discovered incomplete
* tunnels may be %NULL or null adapter port instead.
* @npaths: Number of paths in @paths
* @pre_activate: Optional tunnel specific initialization called before
* activation. Can touch hardware.
* @activate: Optional tunnel specific activation/deactivation
* @post_deactivate: Optional tunnel specific de-initialization called
* after deactivation. Can touch hardware.
* @destroy: Optional tunnel specific callback called when the tunnel
* memory is being released. Should not touch hardware.
* @maximum_bandwidth: Returns maximum possible bandwidth for this tunnel
* @allocated_bandwidth: Return how much bandwidth is allocated for the tunnel
* @alloc_bandwidth: Change tunnel bandwidth allocation
* @consumed_bandwidth: Return how much bandwidth the tunnel consumes
* @release_unused_bandwidth: Release all unused bandwidth
* @reclaim_available_bandwidth: Reclaim back available bandwidth
* @list: Tunnels are linked using this field
* @type: Type of the tunnel
* @state: Current state of the tunnel
* @max_up: Maximum upstream bandwidth (Mb/s) available for the tunnel.
* Only set if the bandwidth needs to be limited.
* @max_down: Maximum downstream bandwidth (Mb/s) available for the tunnel.
* Only set if the bandwidth needs to be limited.
* @allocated_up: Allocated upstream bandwidth (only for USB3)
* @allocated_down: Allocated downstream bandwidth (only for USB3)
* @bw_mode: DP bandwidth allocation mode registers can be used to
* determine consumed and allocated bandwidth
* @dprx_started: DPRX negotiation was started (tb_dp_dprx_start() was called for it)
* @dprx_canceled: Was DPRX capabilities read poll canceled
* @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes
* @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read
* @callback: Optional callback called when DP tunnel is fully activated
* @callback_data: Optional data for @callback
* @paths: All paths required by the tunnel
*/
struct tb_tunnel {
struct kref kref;
struct tb *tb;
struct tb_port *src_port;
struct tb_port *dst_port;
size_t npaths;
int (*pre_activate)(struct tb_tunnel *tunnel);
int (*activate)(struct tb_tunnel *tunnel, bool activate);
void (*post_deactivate)(struct tb_tunnel *tunnel);
void (*destroy)(struct tb_tunnel *tunnel);
int (*maximum_bandwidth)(struct tb_tunnel *tunnel, int *max_up,
int *max_down);
int (*allocated_bandwidth)(struct tb_tunnel *tunnel, int *allocated_up,
int *allocated_down);
int (*alloc_bandwidth)(struct tb_tunnel *tunnel, int *alloc_up,
int *alloc_down);
int (*consumed_bandwidth)(struct tb_tunnel *tunnel, int *consumed_up,
int *consumed_down);
int (*release_unused_bandwidth)(struct tb_tunnel *tunnel);
void (*reclaim_available_bandwidth)(struct tb_tunnel *tunnel,
int *available_up,
int *available_down);
struct list_head list;
enum tb_tunnel_type type;
enum tb_tunnel_state state;
int max_up;
int max_down;
int allocated_up;
int allocated_down;
bool bw_mode;
bool dprx_started;
bool dprx_canceled;
ktime_t dprx_timeout;
struct delayed_work dprx_work;
void (*callback)(struct tb_tunnel *tunnel, void *data);
void *callback_data;
struct tb_path *paths[] __counted_by(npaths);
};
struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down,
bool alloc_hopid);
struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
struct tb_port *down);
bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up,
int *reserved_down);
struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in,
bool alloc_hopid);
struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
struct tb_port *out, int link_nr,
int max_up, int max_down,
void (*callback)(struct tb_tunnel *, void *),
void *callback_data);
struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
struct tb_port *dst, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring);
bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path,
int transmit_ring, int receive_path, int receive_ring);
struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down,
bool alloc_hopid);
struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up,
struct tb_port *down, int max_up,
int max_down);
void tb_tunnel_put(struct tb_tunnel *tunnel);
int tb_tunnel_activate(struct tb_tunnel *tunnel);
void tb_tunnel_deactivate(struct tb_tunnel *tunnel);
/**
* tb_tunnel_is_active() - Is tunnel fully activated
* @tunnel: Tunnel to check
*
* Return: %true if @tunnel is fully activated.
*
* Note for DP tunnels this returns %true only once the DPRX capabilities
* read has been issued successfully. For other tunnels, this function
* returns %true pretty much once tb_tunnel_activate() returns successfully.
*/
static inline bool tb_tunnel_is_active(const struct tb_tunnel *tunnel)
{
return tunnel->state == TB_TUNNEL_ACTIVE;
}
bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel);
bool tb_tunnel_port_on_path(const struct tb_tunnel *tunnel,
const struct tb_port *port);
int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up,
int *max_down);
int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up,
int *allocated_down);
int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
int *alloc_down);
int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
int *consumed_down);
int tb_tunnel_release_unused_bandwidth(struct tb_tunnel *tunnel);
void tb_tunnel_reclaim_available_bandwidth(struct tb_tunnel *tunnel,
int *available_up,
int *available_down);
static inline bool tb_tunnel_is_pci(const struct tb_tunnel *tunnel)
{
return tunnel->type == TB_TUNNEL_PCI;
}
static inline bool tb_tunnel_is_dp(const struct tb_tunnel *tunnel)
{
return tunnel->type == TB_TUNNEL_DP;
}
static inline bool tb_tunnel_is_dma(const struct tb_tunnel *tunnel)
{
return tunnel->type == TB_TUNNEL_DMA;
}
static inline bool tb_tunnel_is_usb3(const struct tb_tunnel *tunnel)
{
return tunnel->type == TB_TUNNEL_USB3;
}
static inline bool tb_tunnel_direction_downstream(const struct tb_tunnel *tunnel)
{
return tb_port_path_direction_downstream(tunnel->src_port,
tunnel->dst_port);
}
/**
* enum tb_tunnel_event - Tunnel related events
* @TB_TUNNEL_ACTIVATED: A tunnel was activated
* @TB_TUNNEL_CHANGED: There is a tunneling change in the domain. Includes
* full %TUNNEL_DETAILS if the tunnel in question is known
* (ICM does not provide that information).
* @TB_TUNNEL_DEACTIVATED: A tunnel was torn down
* @TB_TUNNEL_LOW_BANDWIDTH: Tunnel bandwidth is not optimal
* @TB_TUNNEL_NO_BANDWIDTH: There is not enough bandwidth for a tunnel
*/
enum tb_tunnel_event {
TB_TUNNEL_ACTIVATED,
TB_TUNNEL_CHANGED,
TB_TUNNEL_DEACTIVATED,
TB_TUNNEL_LOW_BANDWIDTH,
TB_TUNNEL_NO_BANDWIDTH,
};
void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event,
enum tb_tunnel_type type,
const struct tb_port *src_port,
const struct tb_port *dst_port);
const char *tb_tunnel_type_name(const struct tb_tunnel *tunnel);
#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \
do { \
struct tb_tunnel *__tunnel = (tunnel); \
level(__tunnel->tb, "%llx:%u <-> %llx:%u (%s): " fmt, \
tb_route(__tunnel->src_port->sw), \
__tunnel->src_port->port, \
tb_route(__tunnel->dst_port->sw), \
__tunnel->dst_port->port, \
tb_tunnel_type_name(__tunnel), \
## arg); \
} while (0)
#define tb_tunnel_WARN(tunnel, fmt, arg...) \
__TB_TUNNEL_PRINT(tb_WARN, tunnel, fmt, ##arg)
#define tb_tunnel_warn(tunnel, fmt, arg...) \
__TB_TUNNEL_PRINT(tb_warn, tunnel, fmt, ##arg)
#define tb_tunnel_info(tunnel, fmt, arg...) \
__TB_TUNNEL_PRINT(tb_info, tunnel, fmt, ##arg)
#define tb_tunnel_dbg(tunnel, fmt, arg...) \
__TB_TUNNEL_PRINT(tb_dbg, tunnel, fmt, ##arg)
#endif
File diff suppressed because it is too large Load Diff
+366
View File
@@ -0,0 +1,366 @@
// SPDX-License-Identifier: GPL-2.0
/*
* USB4 port device
*
* Copyright (C) 2021, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/pm_runtime.h>
#include <linux/component.h>
#include <linux/property.h>
#include "tb.h"
static int connector_bind(struct device *dev, struct device *connector, void *data)
{
int ret;
ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector");
if (ret)
return ret;
ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev));
if (ret)
sysfs_remove_link(&dev->kobj, "connector");
return ret;
}
static void connector_unbind(struct device *dev, struct device *connector, void *data)
{
sysfs_remove_link(&connector->kobj, dev_name(dev));
sysfs_remove_link(&dev->kobj, "connector");
}
static const struct component_ops connector_ops = {
.bind = connector_bind,
.unbind = connector_unbind,
};
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 if (port->xdomain)
link = port->xdomain->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 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);
}
/**
* usb4_usb3_port_match() - Matches USB4 port device with USB 3.x port device
* @usb4_port_dev: USB4 port device
* @usb3_port_fwnode: USB 3.x port firmware node
*
* Checks if USB 3.x port @usb3_port_fwnode is tunneled through USB4 port @usb4_port_dev.
* Returns true if match is found, false otherwise.
*
* Function is designed to be used with component framework (component_match_add).
*/
bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode)
{
struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = NULL;
struct usb4_port *usb4;
struct tb_switch *sw;
struct tb_nhi *nhi;
u8 usb4_port_num;
struct tb *tb;
usb4 = tb_to_usb4_port_device(usb4_port_dev);
if (!usb4)
return false;
sw = usb4->port->sw;
tb = sw->tb;
nhi = tb->nhi;
nhi_fwnode = fwnode_find_reference(usb3_port_fwnode, "usb4-host-interface", 0);
if (IS_ERR(nhi_fwnode))
return false;
/* Check if USB3 fwnode references same NHI where USB4 port resides */
if (!device_match_fwnode(nhi->dev, nhi_fwnode))
return false;
if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num))
return false;
return usb4_port_index(sw, usb4->port) == usb4_port_num;
}
EXPORT_SYMBOL_GPL(usb4_usb3_port_match);
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
};
static void usb4_port_device_release(struct device *dev)
{
struct usb4_port *usb4 = container_of(dev, struct usb4_port, dev);
kfree(usb4);
}
const 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.
*
* Return: Pointer to &struct usb4_port or ERR_PTR() in case of an error.
*/
struct usb4_port *usb4_port_device_add(struct tb_port *port)
{
struct usb4_port *usb4;
int ret;
usb4 = kzalloc_obj(*usb4);
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);
}
ret = component_add(&usb4->dev, &connector_ops);
if (ret) {
dev_err(&usb4->dev, "failed to add component\n");
device_unregister(&usb4->dev);
}
if (!tb_is_upstream_port(port))
device_set_wakeup_capable(&usb4->dev, true);
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)
{
component_del(&usb4->dev, &connector_ops);
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.
*
* Return: %0 on success, negative errno otherwise.
*/
int usb4_port_device_resume(struct usb4_port *usb4)
{
return usb4->offline ? usb4_port_offline(usb4) : 0;
}
File diff suppressed because it is too large Load Diff