Merge tag 'thunderbolt-for-v7.2-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt into usb-next

Mika writes:

thunderbolt: Changes for v7.2 merge window

This includes following USB4/Thunderbolt changes for the v7.2 merge
window:

  - Make the driver more compliant with the connection manager guide.
  - Improvements over Thunderbolt XDomain service handling.
  - USB4STREAM driver.
  - Split out PCIe bits into pci.c to allow the driver to work on
    non-PCIe hosts as well.
  - Various fixes and improvements.

All these have been in linux-next with no reported issues.

* tag 'thunderbolt-for-v7.2-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt: (41 commits)
  thunderbolt: debugfs: Fix sideband write size check
  thunderbolt: debugfs: Fix margining error counter buffer leak
  thunderbolt: test: Release third DP tunnel
  thunderbolt: Prevent XDomain delayed work use-after-free on disconnect
  thunderbolt: test: Add KUnit tests for property parser bounds checks
  thunderbolt: Add some more descriptive probe error messages
  thunderbolt: Require nhi->ops be valid
  thunderbolt: Separate out common NHI bits
  thunderbolt: Move pci_device out of tb_nhi
  thunderbolt: Increase Notification Timeout to 255 ms for USB4 routers
  thunderbolt: Increase timeout for Configuration Ready bit
  thunderbolt: Verify Router Ready bit is set after router enumeration
  thunderbolt: Verify PCIe adapter in detect state before tunnel setup
  thunderbolt: Activate path hops from source to destination
  thunderbolt: Fix lane bonding log when bonding not possible
  thunderbolt: Don't access path config space on Lane 1 adapters in tb_switch_reset_host()
  thunderbolt: Improve multi-display DisplayPort tunnel allocation
  docs: admin-guide: thunderbolt: Add instructions how to use USB4STREAM
  thunderbolt: Add support for USB4STREAM
  thunderbolt: Add support for ConfigFS
  ...
This commit is contained in:
Greg Kroah-Hartman
2026-06-12 10:44:24 +02:00
31 changed files with 3717 additions and 926 deletions

View File

@@ -0,0 +1,83 @@
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
Configuration group for a stream Thunderbolt/USB4
service. It is possible to create groups even if there
is no connection yet to the other host. Once a
connection established and there is stream service on
the remote side that matches, this configuration is
applied to it.
To find the service name you can run tblist from tbtools [1]:
# tblist -A
...
Domain 0 Route 3 Index 0: stream
[1] https://github.com/intel/tbtools
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
Creates new stream with $name and fills it with the
default values. If there is an advertised remote stream
with the same name, uses its values as the default.
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/index
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
This matches the X in /dev/tbstreamX and allows userspace
to map the configfs directory to the corresponding character
device.
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/in_hopid
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
In HopID used with the read path of the tunnel. Available HopIDs
for tunneling start from 8. You can pass also -1 for automatic
allocation. The allocated value can be read here. Writing 0 will
de-allocate if the stream is not in use.
To figure out the maximum HopID you can run tbget from
tbtools for the lane adapter. For example below we check
for lane adapter number 1 (first USB4 port):
# tbget -r 0 -a 1 -D ADP_CS_5.Max\ Input\ HopID
19
This allows to use anything between 8 and 19 inclusive.
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/out_hopid
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
Out HopID used with the write path of the tunnel. Available HopIDs
for tunneling start from 8. You can pass also -1 for automatic
allocation. The allocated value can be read here. Writing 0 will
de-allocate if the stream is not in use. See @in_hopid
for how to figure out the maximum HopID.
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/ring_size
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
Size of the TX/RX rings. Can be adjusted between 32 and
4096. The default is 256.
What: /sys/kernel/config/thunderbolt/stream/<xdomain>.<service>/$name/throttling
Date: Sep 2026
KernelVersion: v7.2
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description:
Interrupt throttling rate in ns. Lower values can give
better latency. The default is 8192 ns.

View File

@@ -373,6 +373,67 @@ port which are named like ``thunderbolt0`` and so on. From this point
you can either use standard userspace tools like ``ip`` to
configure the interface or let your GUI handle it automatically.
Streaming data directly over Thunderbolt cable
----------------------------------------------
In addition to Thunderbolt networking (aka. USB4NET) Linux supports
streaming data directly over a cable as well (aka. USB4STREAM). This is
possible through ``thunderbolt-stream`` driver.
Similarly to ``thunderbolt-net`` you load the driver first on one end::
host1 # modprobe thunderbolt-stream
Then you configure it via ``ConfigFS``::
host1 # cd /sys/kernel/config/thunderbolt/stream
host1 # mkdir -p 0-1.0/data
host1 # cd 0-1.0
host1 # echo -1 > data/in_hopid
host1 # echo -1 > data/out_hopid
This information is automatically announced to the other side via
XDomain properties so if you have cable connected the other side knows
that there is a stream named ``data`` available and can configure it for
you automatically::
host2 # cd /sys/kernel/config/thunderbolt/stream
host2 # mkdir -p 0-3.0/data
Here we used auto-configuration but you can configure it manually too.
In that case you need to fill ``in_hopid`` and ``out_hopid`` accordingly.
If you set them to ``-1`` the next available HopID is used which is
typically what we want.
Once they are configured you can use ``/dev/tbstreamX`` on both sides to
transfer data::
host2 # cat /dev/tbstream0
host1 # dmesg > /dev/tbstream0
Once you are done with the stream you can remove them::
host2 # cd /sys/kernel/config/thunderbolt/stream
host2 # rmdir -p 0-1.0/data
host1 # cd /sys/kernel/config/thunderbolt/stream
host1 # rmdir -p 0-3.0/data
Since streams are essentially files you can use any existing application
that supports ``read(2)`` and ``write(2)`` in some form.
It is possible to have more than one stream and you can have both stream
and ``thunderbolt-net`` in use simultaneously. For example we can create
two streams with name ``control`` and ``data`` like this::
host1 # cd /sys/kernel/config/thunderbolt/stream
host1 # mkdir 0-1.0
host1 # cd 0-1.0
host1 # mkdir control
host1 # mkdir data
Then you have ``/dev/tbstream0`` for ``control`` and ``/dev/tbstream1``
for ``data``. Before you can use them you need to configure them as
shown above for the one stream case.
Forcing power
-------------
Many OEMs include a method that can be used to force the power of a

View File

@@ -34,11 +34,12 @@
#define TBNET_RING_SIZE 256
#define TBNET_LOGIN_RETRIES 60
#define TBNET_LOGOUT_RETRIES 10
#define TBNET_THROTTLING 128000
#define TBNET_E2E BIT(0)
#define TBNET_MATCH_FRAGS_ID BIT(1)
#define TBNET_64K_FRAMES BIT(2)
#define TBNET_MAX_MTU SZ_64K
#define TBNET_FRAME_SIZE SZ_4K
#define TBNET_FRAME_SIZE TB_MAX_FRAME_SIZE
#define TBNET_MAX_PAYLOAD_SIZE \
(TBNET_FRAME_SIZE - sizeof(struct thunderbolt_ip_frame_header))
/* Rx packets need to hold space for skb_shared_info */
@@ -327,11 +328,6 @@ static void stop_login(struct tbnet *net)
netdev_dbg(net->dev, "login stopped\n");
}
static inline unsigned int tbnet_frame_size(const struct tbnet_frame *tf)
{
return tf->frame.size ? : TBNET_FRAME_SIZE;
}
static void tbnet_free_buffers(struct tbnet_ring *ring)
{
unsigned int i;
@@ -562,7 +558,7 @@ static struct tbnet_frame *tbnet_get_tx_buffer(struct tbnet *net)
tf->frame.size = 0;
dma_sync_single_for_cpu(dma_dev, tf->frame.buffer_phy,
tbnet_frame_size(tf), DMA_TO_DEVICE);
tb_ring_frame_size(&tf->frame), DMA_TO_DEVICE);
return tf;
}
@@ -744,7 +740,7 @@ static bool tbnet_check_frame(struct tbnet *net, const struct tbnet_frame *tf,
}
/* Should be greater than just header i.e. contains data */
size = tbnet_frame_size(tf);
size = tb_ring_frame_size(&tf->frame);
if (size <= sizeof(*hdr)) {
net->stats.rx_length_errors++;
return false;
@@ -961,6 +957,9 @@ static int tbnet_open(struct net_device *dev)
}
net->rx_ring.ring = ring;
tb_ring_throttling(net->tx_ring.ring, TBNET_THROTTLING);
tb_ring_throttling(net->rx_ring.ring, TBNET_THROTTLING);
napi_enable(&net->napi);
start_login(net);
@@ -1011,7 +1010,8 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb,
hdr->frame_index, hdr->frame_count);
dma_sync_single_for_device(dma_dev,
frames[i]->frame.buffer_phy,
tbnet_frame_size(frames[i]), DMA_TO_DEVICE);
tb_ring_frame_size(&frames[i]->frame),
DMA_TO_DEVICE);
}
return true;
@@ -1085,7 +1085,7 @@ static bool tbnet_xmit_csum_and_map(struct tbnet *net, struct sk_buff *skb,
*/
for (i = 0; i < frame_count; i++) {
dma_sync_single_for_device(dma_dev, frames[i]->frame.buffer_phy,
tbnet_frame_size(frames[i]), DMA_TO_DEVICE);
tb_ring_frame_size(&frames[i]->frame), DMA_TO_DEVICE);
}
return true;

View File

@@ -18,6 +18,10 @@ menuconfig USB4
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
@@ -60,4 +64,15 @@ config USB4_DMA_TEST
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

View File

@@ -1,14 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y := -I$(src)
obj-${CONFIG_USB4} := thunderbolt.o
thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o
thunderbolt-objs := 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

View File

@@ -28,7 +28,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
return AE_OK;
/* It needs to reference this NHI */
if (dev_fwnode(&nhi->pdev->dev) != fwnode)
if (dev_fwnode(nhi->dev) != fwnode)
goto out_put;
/*
@@ -57,16 +57,16 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
*/
pm_runtime_get_sync(&pdev->dev);
link = device_link_add(&pdev->dev, &nhi->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->pdev->dev, "created link from %s\n",
dev_dbg(nhi->dev, "created link from %s\n",
dev_name(&pdev->dev));
*(bool *)ret = true;
} else {
dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
dev_warn(nhi->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
@@ -93,7 +93,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi)
acpi_status status;
bool ret = false;
if (!has_acpi_companion(&nhi->pdev->dev))
if (!has_acpi_companion(nhi->dev))
return false;
/*
@@ -103,7 +103,7 @@ bool tb_acpi_add_links(struct tb_nhi *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->pdev->dev, "failed to enumerate tunneled ports\n");
dev_warn(nhi->dev, "failed to enumerate tunneled ports\n");
return false;
}
@@ -305,7 +305,7 @@ static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw)
struct tb_nhi *nhi = sw->tb->nhi;
struct acpi_device *parent_adev;
parent_adev = ACPI_COMPANION(&nhi->pdev->dev);
parent_adev = ACPI_COMPANION(nhi->dev);
if (parent_adev)
adev = acpi_find_child_device(parent_adev, 0, false);
}

View File

@@ -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);
}

View File

@@ -56,22 +56,22 @@ struct tb_ctl {
#define tb_ctl_WARN(ctl, format, arg...) \
dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg)
dev_WARN((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_err(ctl, format, arg...) \
dev_err(&(ctl)->nhi->pdev->dev, format, ## arg)
dev_err((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_warn(ctl, format, arg...) \
dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg)
dev_warn((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_info(ctl, format, arg...) \
dev_info(&(ctl)->nhi->pdev->dev, format, ## arg)
dev_info((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_dbg(ctl, format, arg...) \
dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg)
dev_dbg((ctl)->nhi->dev, format, ## arg)
#define tb_ctl_dbg_once(ctl, format, arg...) \
dev_dbg_once(&(ctl)->nhi->pdev->dev, format, ## arg)
dev_dbg_once((ctl)->nhi->dev, format, ## arg)
static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue);
/* Serializes access to request kref_get/put */
@@ -666,8 +666,8 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec,
mutex_init(&ctl->request_queue_lock);
INIT_LIST_HEAD(&ctl->request_queue);
ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev,
TB_FRAME_SIZE, 4, 0);
ctl->frame_pool = dma_pool_create("thunderbolt_ctl", nhi->dev,
TB_FRAME_SIZE, 4, 0);
if (!ctl->frame_pool)
goto err;

View File

@@ -366,7 +366,7 @@ static ssize_t sb_regs_write(struct tb_port *port, const struct sb_reg *sb_regs,
if (!sb_reg)
return -EINVAL;
if (bytes_read > sb_regs->size)
if (bytes_read > sb_reg->size)
return -E2BIG;
ret = usb4_port_sb_write(port, target, index, sb_reg->reg, data,
@@ -956,7 +956,9 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
else if (!strcmp(buf, "stop"))
error_counter = USB4_MARGIN_SW_ERROR_COUNTER_STOP;
else
return -EINVAL;
goto err_free;
free_page((unsigned long)buf);
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
if (!margining->software)
@@ -966,6 +968,10 @@ margining_error_counter_write(struct file *file, const char __user *user_buf,
}
return count;
err_free:
free_page((unsigned long)buf);
return -EINVAL;
}
static int margining_error_counter_show(struct seq_file *s, void *not_used)
@@ -1780,6 +1786,8 @@ static void margining_port_remove(struct tb_port *port)
if (!port->usb4)
return;
if (!port->usb4->margining)
return;
snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
@@ -2361,8 +2369,10 @@ static int sb_regs_show(struct tb_port *port, const struct sb_reg *sb_regs,
memset(data, 0, sizeof(data));
ret = usb4_port_sb_read(port, target, index, regs->reg, data,
regs->size);
if (ret)
return ret;
if (ret) {
seq_printf(s, "0x%02x <not accessible>\n", regs->reg);
continue;
}
seq_printf(s, "0x%02x", regs->reg);
for (j = 0; j < regs->size; j++)

View File

@@ -87,7 +87,6 @@ static const char * const dma_test_result_names[] = {
* @error_code: Error code of the last run
* @complete: Used to wait for the Rx to complete
* @lock: Lock serializing access to this structure
* @debugfs_dir: dentry of this dma_test
*/
struct dma_test {
const struct tb_service *svc;
@@ -108,7 +107,6 @@ struct dma_test {
enum dma_test_test_error error_code;
struct completion complete;
struct mutex lock;
struct dentry *debugfs_dir;
};
/* DMA test property directory UUID: 3188cd10-6523-4a5a-a682-fdca07a248d8 */
@@ -157,6 +155,8 @@ static int dma_test_start_rings(struct dma_test *dt)
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);
@@ -164,6 +164,7 @@ static int dma_test_start_rings(struct dma_test *dt)
}
dt->tx_hopid = ret;
}
if (dt->packets_to_receive) {
@@ -182,6 +183,8 @@ static int dma_test_start_rings(struct dma_test *dt)
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);
@@ -619,18 +622,18 @@ DEFINE_SHOW_ATTRIBUTE(status);
static void dma_test_debugfs_init(struct tb_service *svc)
{
struct dma_test *dt = tb_service_get_drvdata(svc);
struct dentry *debugfs_dir;
dt->debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir);
debugfs_create_file("lanes", 0600, dt->debugfs_dir, svc, &lanes_fops);
debugfs_create_file("speed", 0600, dt->debugfs_dir, svc, &speed_fops);
debugfs_create_file("packets_to_receive", 0600, dt->debugfs_dir, svc,
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, dt->debugfs_dir, svc,
debugfs_create_file("packets_to_send", 0600, debugfs_dir, svc,
&packets_to_send_fops);
debugfs_create_file("status", 0400, dt->debugfs_dir, svc, &status_fops);
debugfs_create_file("test", 0200, dt->debugfs_dir, svc, &test_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)
@@ -658,7 +661,7 @@ static void dma_test_remove(struct tb_service *svc)
struct dma_test *dt = tb_service_get_drvdata(svc);
mutex_lock(&dt->lock);
debugfs_remove_recursive(dt->debugfs_dir);
debugfs_lookup_and_remove("dma_test", svc->debugfs_dir);
mutex_unlock(&dt->lock);
}

View File

@@ -319,12 +319,15 @@ const struct bus_type tb_bus_type = {
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 = {
@@ -402,7 +405,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
if (!tb->ctl)
goto err_destroy_wq;
tb->dev.parent = &nhi->pdev->dev;
tb->dev.parent = nhi->dev;
tb->dev.bus = &tb_bus_type;
tb->dev.type = &tb_domain_type;
tb->dev.groups = domain_attr_groups;
@@ -850,10 +853,41 @@ int tb_domain_disconnect_all_paths(struct tb *tb)
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();
@@ -883,4 +917,5 @@ void tb_domain_exit(void)
tb_xdomain_exit();
tb_acpi_exit();
tb_debugfs_exit();
tb_configfs_exit();
}

View File

@@ -465,7 +465,7 @@ static void tb_switch_drom_free(struct tb_switch *sw)
*/
static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
{
struct device *dev = &sw->tb->nhi->pdev->dev;
struct device *dev = sw->tb->nhi->dev;
int len, res;
len = device_property_count_u8(dev, "ThunderboltDROM");

View File

@@ -587,6 +587,11 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
struct icm_fr_pkg_approve_xdomain request;
int ret;
if (atomic_read(&xd->ntunnels) >= 1) {
tb_warn(tb, "only one tunnel is supported by the firmware\n");
return -EOPNOTSUPP;
}
memset(&request, 0, sizeof(request));
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link;
@@ -738,6 +743,7 @@ static void remove_xdomain(struct tb_xdomain *xd)
sw = tb_to_switch(xd->dev.parent);
tb_port_at(xd->route, sw)->xdomain = NULL;
xd->is_unplugged = true;
tb_xdomain_remove(xd);
}
@@ -1157,6 +1163,11 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
struct icm_tr_pkg_approve_xdomain request;
int ret;
if (atomic_read(&xd->ntunnels) >= 1) {
tb_warn(tb, "only one tunnel is supported by the firmware\n");
return -EOPNOTSUPP;
}
memset(&request, 0, sizeof(request));
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.route_hi = upper_32_bits(xd->route);
@@ -1455,6 +1466,7 @@ static struct pci_dev *get_upstream_port(struct pci_dev *pdev)
static bool icm_ar_is_supported(struct tb *tb)
{
struct pci_dev *pdev = to_pci_dev(tb->nhi->dev);
struct pci_dev *upstream_port;
struct icm *icm = tb_priv(tb);
@@ -1472,7 +1484,7 @@ static bool icm_ar_is_supported(struct tb *tb)
* Find the upstream PCIe port in case we need to do reset
* through its vendor specific registers.
*/
upstream_port = get_upstream_port(tb->nhi->pdev);
upstream_port = get_upstream_port(pdev);
if (upstream_port) {
int cap;
@@ -1508,7 +1520,7 @@ static int icm_ar_get_mode(struct tb *tb)
} while (--retries);
if (!retries) {
dev_err(&nhi->pdev->dev, "ICM firmware not authenticated\n");
dev_err(nhi->dev, "ICM firmware not authenticated\n");
return -ENODEV;
}
@@ -1674,11 +1686,11 @@ icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level,
static void icm_icl_set_uuid(struct tb *tb)
{
struct tb_nhi *nhi = tb->nhi;
struct pci_dev *pdev = to_pci_dev(tb->nhi->dev);
u32 uuid[4];
pci_read_config_dword(nhi->pdev, VS_CAP_10, &uuid[0]);
pci_read_config_dword(nhi->pdev, VS_CAP_11, &uuid[1]);
pci_read_config_dword(pdev, VS_CAP_10, &uuid[0]);
pci_read_config_dword(pdev, VS_CAP_11, &uuid[1]);
uuid[2] = 0xffffffff;
uuid[3] = 0xffffffff;
@@ -1762,6 +1774,8 @@ static void icm_handle_notification(struct work_struct *work)
kfree(n->pkg);
kfree(n);
tb_domain_unregister_unplugged_xdomains(tb);
}
static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
@@ -1853,7 +1867,7 @@ static int icm_firmware_start(struct tb *tb, struct tb_nhi *nhi)
if (icm_firmware_running(nhi))
return 0;
dev_dbg(&nhi->pdev->dev, "starting ICM firmware\n");
dev_dbg(nhi->dev, "starting ICM firmware\n");
ret = icm_firmware_reset(tb, nhi);
if (ret)
@@ -1948,7 +1962,7 @@ static int icm_firmware_init(struct tb *tb)
ret = icm_firmware_start(tb, nhi);
if (ret) {
dev_err(&nhi->pdev->dev, "could not start ICM firmware\n");
dev_err(nhi->dev, "could not start ICM firmware\n");
return ret;
}
@@ -1980,10 +1994,10 @@ static int icm_firmware_init(struct tb *tb)
*/
ret = icm_reset_phy_port(tb, 0);
if (ret)
dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n");
dev_warn(nhi->dev, "failed to reset links on port0\n");
ret = icm_reset_phy_port(tb, 1);
if (ret)
dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n");
dev_warn(nhi->dev, "failed to reset links on port1\n");
return 0;
}
@@ -2112,6 +2126,8 @@ static void icm_rescan_work(struct work_struct *work)
if (tb->root_switch)
icm_free_unplugged_children(tb->root_switch);
mutex_unlock(&tb->lock);
tb_domain_unregister_unplugged_xdomains(tb);
}
static void icm_complete(struct tb *tb)
@@ -2462,6 +2478,7 @@ static const struct tb_cm_ops icm_icl_ops = {
struct tb *icm_probe(struct tb_nhi *nhi)
{
struct pci_dev *pdev = to_pci_dev(nhi->dev);
struct icm *icm;
struct tb *tb;
@@ -2473,7 +2490,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
INIT_DELAYED_WORK(&icm->rescan_work, icm_rescan_work);
mutex_init(&icm->request_lock);
switch (nhi->pdev->device) {
switch (pdev->device) {
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
icm->can_upgrade_nvm = true;
@@ -2579,7 +2596,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
}
if (!icm->is_supported || !icm->is_supported(tb)) {
dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n");
dev_dbg(nhi->dev, "ICM not supported on this controller\n");
tb_domain_put(tb);
return NULL;
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,14 @@ enum nhi_mailbox_cmd {
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
@@ -38,6 +46,12 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi);
* @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);
@@ -46,10 +60,14 @@ struct tb_nhi_ops {
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);
};
extern const struct tb_nhi_ops icl_nhi_ops;
/*
* 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
@@ -100,4 +118,15 @@ extern const struct tb_nhi_ops icl_nhi_ops;
#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

View File

@@ -1,185 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NHI specific operations
*
* Copyright (C) 2019, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/delay.h>
#include <linux/suspend.h>
#include "nhi.h"
#include "nhi_regs.h"
#include "tb.h"
/* 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 = pci_get_drvdata(nhi->pdev);
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)
{
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(nhi->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(nhi->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(nhi->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)
{
u32 data;
data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);
}
static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
{
unsigned long end;
u32 data;
if (!timeout)
goto clear;
end = jiffies + msecs_to_jiffies(timeout);
do {
pci_read_config_dword(nhi->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(nhi->pdev, VS_CAP_19, 0);
return 0;
}
static void icl_nhi_set_ltr(struct tb_nhi *nhi)
{
u32 max_ltr, ltr;
pci_read_config_dword(nhi->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(nhi->pdev, VS_CAP_15, ltr);
}
static int icl_nhi_suspend(struct tb_nhi *nhi)
{
struct tb *tb = pci_get_drvdata(nhi->pdev);
int ret;
if (icl_nhi_is_device_connected(nhi))
return 0;
if (tb_switch_is_icm(tb->root_switch)) {
/*
* If there is no device connected we need to perform
* both: a handshake through LC mailbox and force power
* down before entering D3.
*/
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
if (ret)
return ret;
}
return icl_nhi_force_power(nhi, false);
}
static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
{
struct tb *tb = pci_get_drvdata(nhi->pdev);
enum icl_lc_mailbox_cmd cmd;
if (!pm_suspend_via_firmware())
return icl_nhi_suspend(nhi);
if (!tb_switch_is_icm(tb->root_switch))
return 0;
cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
icl_nhi_lc_mailbox_cmd(nhi, cmd);
return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
}
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)
{
icl_nhi_force_power(nhi, false);
}
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,
};

View File

@@ -101,7 +101,8 @@ struct ring_desc {
#define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208
#define REG_INT_THROTTLING_RATE 0x38c00
#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

View File

@@ -412,7 +412,8 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
* in the USB4 spec so we clear them
* only for pre-USB4 adapters.
*/
if (!tb_switch_is_usb4(port->sw)) {
if (tb_port_is_null(port) ||
!tb_switch_is_usb4(port->sw)) {
hop.ingress_fc = 0;
hop.ingress_shared_buffer = 0;
}
@@ -483,7 +484,7 @@ void tb_path_deactivate(struct tb_path *path)
* tb_path_activate() - activate a path
* @path: Path to activate
*
* Activate a path starting with the last hop and iterating backwards. The
* 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.
@@ -525,22 +526,25 @@ int tb_path_activate(struct tb_path *path)
}
/* Activate hops. */
for (i = path->path_length - 1; i >= 0; i--) {
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);
/* dword 0 */
/* 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.initial_credits = path->hops[i].initial_credits;
hop.pmps = path->hops[i].pm_support;
hop.unknown1 = 0;
hop.enable = 1;
/* dword 1 */
out_mask = (i == path->path_length - 1) ?
TB_PATH_DESTINATION : TB_PATH_INTERNAL;
in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL;
@@ -550,12 +554,21 @@ int tb_path_activate(struct tb_path *path)
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.ingress_fc = path->ingress_fc_enable & in_mask;
hop.egress_fc = path->egress_fc_enable & out_mask;
hop.ingress_shared_buffer = path->ingress_shared_buffer
& in_mask;
hop.egress_shared_buffer = path->egress_shared_buffer
& 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);
@@ -563,7 +576,7 @@ int tb_path_activate(struct tb_path *path)
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, i);
__tb_path_deactivate_hops(path, 0);
__tb_path_deallocate_nfc(path, 0);
goto err;
}

622
drivers/thunderbolt/pci.c Normal file
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);

View File

@@ -40,6 +40,7 @@ struct tb_property_dir_entry {
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)
{
@@ -521,17 +522,9 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
return ret < 0 ? ret : 0;
}
/**
* 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)
static struct tb_property_dir *copy_dir(const struct tb_property_dir *dir)
{
struct tb_property *property, *p = NULL;
struct tb_property *property, *p;
struct tb_property_dir *d;
if (!dir)
@@ -542,56 +535,131 @@ struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
return NULL;
list_for_each_entry(property, &dir->properties, list) {
struct tb_property *p;
p = tb_property_alloc(property->key, property->type);
p = tb_property_copy(property);
if (!p)
goto err_free;
p->length = property->length;
switch (property->type) {
case TB_PROPERTY_TYPE_DIRECTORY:
p->value.dir = tb_property_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;
}
list_add_tail(&p->list, &d->properties);
}
return d;
err_free:
kfree(p);
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

1698
drivers/thunderbolt/stream.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -209,30 +209,6 @@ static int nvm_authenticate_device_dma_port(struct tb_switch *sw)
return -ETIMEDOUT;
}
static void nvm_authenticate_start_dma_port(struct tb_switch *sw)
{
struct pci_dev *root_port;
/*
* During host router NVM upgrade we should not allow root port to
* go into D3cold because some root ports cannot trigger PME
* itself. To be on the safe side keep the root port in D0 during
* the whole upgrade process.
*/
root_port = pcie_find_root_port(sw->tb->nhi->pdev);
if (root_port)
pm_runtime_get_noresume(&root_port->dev);
}
static void nvm_authenticate_complete_dma_port(struct tb_switch *sw)
{
struct pci_dev *root_port;
root_port = pcie_find_root_port(sw->tb->nhi->pdev);
if (root_port)
pm_runtime_put(&root_port->dev);
}
static inline bool nvm_readable(struct tb_switch *sw)
{
if (tb_switch_is_usb4(sw)) {
@@ -258,6 +234,7 @@ static inline bool nvm_upgradeable(struct tb_switch *sw)
static int nvm_authenticate(struct tb_switch *sw, bool auth_only)
{
struct tb_nhi *nhi = sw->tb->nhi;
int ret;
if (tb_switch_is_usb4(sw)) {
@@ -274,7 +251,8 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only)
sw->nvm->authenticating = true;
if (!tb_route(sw)) {
nvm_authenticate_start_dma_port(sw);
if (nhi->ops->pre_nvm_auth)
nhi->ops->pre_nvm_auth(nhi);
ret = nvm_authenticate_host_dma_port(sw);
} else {
ret = nvm_authenticate_device_dma_port(sw);
@@ -1620,6 +1598,12 @@ static int tb_switch_reset_host(struct tb_switch *sw)
ret = tb_port_reset(port);
if (ret)
return ret;
/*
* USB4 Lane 1 adapters do not have accessible
* path config space.
*/
if (tb_switch_is_usb4(sw) && !port->usb4)
continue;
} else if (tb_port_is_usb3_down(port) ||
tb_port_is_usb3_up(port)) {
tb_usb3_port_enable(port, false);
@@ -1761,8 +1745,6 @@ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
/*
* tb_plug_events_active() - enable/disable plug events on a switch
*
* Also configures a sane plug_events_delay of 255ms.
*
* Return: %0 on success, negative errno otherwise.
*/
static int tb_plug_events_active(struct tb_switch *sw, bool active)
@@ -1773,11 +1755,6 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw))
return 0;
sw->config.plug_events_delay = 0xff;
res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1);
if (res)
return res;
res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
if (res)
return res;
@@ -2639,6 +2616,8 @@ int tb_switch_configure(struct tb_switch *sw)
sw->config.enabled = 1;
/* Set Notification Timeout to 255 ms for all routers */
sw->config.plug_events_delay = 0xff;
if (tb_switch_is_usb4(sw)) {
/*
* For USB4 devices, we need to program the CM version
@@ -2650,7 +2629,6 @@ int tb_switch_configure(struct tb_switch *sw)
sw->config.cmuv = ROUTER_CS_4_CMUV_V1;
else
sw->config.cmuv = ROUTER_CS_4_CMUV_V2;
sw->config.plug_events_delay = 0xa;
/* Enumerate the switch */
ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
@@ -2671,7 +2649,7 @@ int tb_switch_configure(struct tb_switch *sw)
/* Enumerate the switch */
ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
ROUTER_CS_1, 3);
ROUTER_CS_1, 4);
}
if (ret)
return ret;
@@ -2743,6 +2721,7 @@ static int tb_switch_set_uuid(struct tb_switch *sw)
static int tb_switch_add_dma_port(struct tb_switch *sw)
{
struct tb_nhi *nhi = sw->tb->nhi;
u32 status;
int ret;
@@ -2802,8 +2781,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
*/
nvm_get_auth_status(sw, &status);
if (status) {
if (!tb_route(sw))
nvm_authenticate_complete_dma_port(sw);
if (!tb_route(sw)) {
if (nhi->ops->post_nvm_auth)
nhi->ops->post_nvm_auth(nhi);
}
return 0;
}
@@ -2817,8 +2798,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
return ret;
/* Now we can allow root port to suspend again */
if (!tb_route(sw))
nvm_authenticate_complete_dma_port(sw);
if (!tb_route(sw)) {
if (nhi->ops->post_nvm_auth)
nhi->ops->post_nvm_auth(nhi);
}
if (status) {
tb_sw_info(sw, "switch flash authentication failed\n");
@@ -2971,14 +2954,14 @@ static int tb_switch_lane_bonding_enable(struct tb_switch *sw)
int ret;
if (!tb_switch_lane_bonding_possible(sw))
return 0;
return -EOPNOTSUPP;
up = tb_upstream_port(sw);
down = tb_switch_downstream_port(sw);
if (!tb_port_width_supported(up, TB_LINK_WIDTH_DUAL) ||
!tb_port_width_supported(down, TB_LINK_WIDTH_DUAL))
return 0;
return -EOPNOTSUPP;
/*
* Both lanes need to be in CL0. Here we assume lane 0 already be in
@@ -3625,6 +3608,20 @@ int tb_switch_resume(struct tb_switch *sw, bool runtime)
tb_port_warn(port,
"lost during suspend, disconnecting\n");
tb_sw_set_unplugged(port->remote->sw);
} else if (port->xdomain) {
/*
* If the user replaced the XDomain with
* another router, this will succeed in
* which case we must remove the XDomain
* before adding the new router.
*/
err = tb_cfg_get_upstream_port(sw->tb->ctl,
port->xdomain->route);
if (err > 0) {
tb_port_warn(port,
"XDomain was disconnected\n");
port->xdomain->is_unplugged = true;
}
}
}
}

View File

@@ -98,7 +98,7 @@ static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug)
if (!ev)
return;
ev->tb = tb;
ev->tb = tb_domain_get(tb);
ev->route = route;
ev->port = port;
ev->unplug = unplug;
@@ -2524,9 +2524,14 @@ static void tb_handle_hotplug(struct work_struct *work)
out:
mutex_unlock(&tb->lock);
tb_domain_unregister_unplugged_xdomains(tb);
pm_runtime_mark_last_busy(&tb->dev);
pm_runtime_put_autosuspend(&tb->dev);
/* Undo the refcount increased in tb_queue_hotplug() */
tb_domain_put(tb);
kfree(ev);
}
@@ -2844,6 +2849,9 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work)
/* Update other clients about the allocation change */
tb_recalc_estimated_bandwidth(tb);
tb_dbg(tb, "checking if more DP tunnels can be established now\n");
tb_tunnel_dp(tb);
}
put_sw:
@@ -2949,6 +2957,7 @@ static void tb_stop(struct tb *tb)
tb_tunnel_put(tunnel);
}
tb_switch_remove(tb->root_switch);
tb->root_switch = NULL;
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
}
@@ -2991,7 +3000,8 @@ static int tb_start(struct tb *tb, bool reset)
tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0);
if (IS_ERR(tb->root_switch))
return PTR_ERR(tb->root_switch);
return dev_err_probe(tb->nhi->dev, PTR_ERR(tb->root_switch),
"failed to allocate host router\n");
/*
* ICM firmware upgrade needs running firmware and in native
@@ -3008,14 +3018,14 @@ static int tb_start(struct tb *tb, bool reset)
ret = tb_switch_configure(tb->root_switch);
if (ret) {
tb_switch_put(tb->root_switch);
return ret;
return dev_err_probe(tb->nhi->dev, ret, "failed to configure host router\n");
}
/* Announce the switch to the world */
ret = tb_switch_add(tb->root_switch);
if (ret) {
tb_switch_put(tb->root_switch);
return ret;
return dev_err_probe(tb->nhi->dev, ret, "failed to add host router\n");
}
/*
@@ -3110,6 +3120,24 @@ static void tb_restore_children(struct tb_switch *sw)
}
}
static void tb_free_unplugged_xdomains(struct tb_switch *sw)
{
struct tb_port *port;
tb_switch_for_each_port(sw, port) {
if (tb_is_upstream_port(port))
continue;
if (port->xdomain && port->xdomain->is_unplugged) {
tb_retimer_remove_all(port);
tb_xdomain_remove(port->xdomain);
tb_port_unconfigure_xdomain(port);
port->xdomain = NULL;
} else if (port->remote) {
tb_free_unplugged_xdomains(port->remote->sw);
}
}
}
static int tb_resume_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
@@ -3129,6 +3157,7 @@ static int tb_resume_noirq(struct tb *tb)
tb_switch_resume(tb->root_switch, false);
tb_free_invalid_tunnels(tb);
tb_free_unplugged_children(tb->root_switch);
tb_free_unplugged_xdomains(tb->root_switch);
tb_restore_children(tb->root_switch);
/*
@@ -3171,28 +3200,6 @@ static int tb_resume_noirq(struct tb *tb)
return 0;
}
static int tb_free_unplugged_xdomains(struct tb_switch *sw)
{
struct tb_port *port;
int ret = 0;
tb_switch_for_each_port(sw, port) {
if (tb_is_upstream_port(port))
continue;
if (port->xdomain && port->xdomain->is_unplugged) {
tb_retimer_remove_all(port);
tb_xdomain_remove(port->xdomain);
tb_port_unconfigure_xdomain(port);
port->xdomain = NULL;
ret++;
} else if (port->remote) {
ret += tb_free_unplugged_xdomains(port->remote->sw);
}
}
return ret;
}
static int tb_freeze_noirq(struct tb *tb)
{
struct tb_cm *tcm = tb_priv(tb);
@@ -3212,14 +3219,14 @@ static int tb_thaw_noirq(struct tb *tb)
static void tb_complete(struct tb *tb)
{
/*
* Release any unplugged XDomains and if there is a case where
* Unregister unplugged XDomains and if there is a case where
* another domain is swapped in place of unplugged XDomain we
* need to run another rescan.
*/
mutex_lock(&tb->lock);
if (tb_free_unplugged_xdomains(tb->root_switch))
tb_scan_switch(tb->root_switch);
mutex_unlock(&tb->lock);
if (tb_domain_unregister_unplugged_xdomains(tb)) {
scoped_guard(mutex, &tb->lock)
tb_scan_switch(tb->root_switch);
}
}
static int tb_runtime_suspend(struct tb *tb)
@@ -3246,11 +3253,11 @@ static void tb_remove_work(struct work_struct *work)
struct tb *tb = tcm_to_tb(tcm);
mutex_lock(&tb->lock);
if (tb->root_switch) {
if (tb->root_switch)
tb_free_unplugged_children(tb->root_switch);
tb_free_unplugged_xdomains(tb->root_switch);
}
mutex_unlock(&tb->lock);
tb_free_unplugged_xdomains(tb->root_switch);
}
static int tb_runtime_resume(struct tb *tb)
@@ -3304,13 +3311,14 @@ static const struct tb_cm_ops tb_cm_ops = {
*/
static bool tb_apple_add_links(struct tb_nhi *nhi)
{
struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev);
struct pci_dev *upstream, *pdev;
bool ret;
if (!x86_apple_machine)
return false;
switch (nhi->pdev->device) {
switch (nhi_pdev->device) {
case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
@@ -3320,7 +3328,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi)
return false;
}
upstream = pci_upstream_bridge(nhi->pdev);
upstream = pci_upstream_bridge(nhi_pdev);
while (upstream) {
if (!pci_is_pcie(upstream))
return false;
@@ -3347,15 +3355,15 @@ static bool tb_apple_add_links(struct tb_nhi *nhi)
!pdev->is_pciehp)
continue;
link = device_link_add(&pdev->dev, &nhi->pdev->dev,
link = device_link_add(&pdev->dev, nhi->dev,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_PM_RUNTIME);
if (link) {
dev_dbg(&nhi->pdev->dev, "created link from %s\n",
dev_dbg(nhi->dev, "created link from %s\n",
dev_name(&pdev->dev));
ret = true;
} else {
dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
dev_warn(nhi->dev, "device link creation from %s failed\n",
dev_name(&pdev->dev));
}
}

View File

@@ -725,11 +725,11 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer,
length);
}
#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg)
#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg)
#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg)
#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg)
#define tb_dbg(tb, fmt, arg...) dev_dbg(&(tb)->nhi->pdev->dev, fmt, ## arg)
#define tb_err(tb, fmt, arg...) dev_err((tb)->nhi->dev, fmt, ## arg)
#define tb_WARN(tb, fmt, arg...) dev_WARN((tb)->nhi->dev, fmt, ## arg)
#define tb_warn(tb, fmt, arg...) dev_warn((tb)->nhi->dev, fmt, ## arg)
#define tb_info(tb, fmt, arg...) dev_info((tb)->nhi->dev, fmt, ## arg)
#define tb_dbg(tb, fmt, arg...) dev_dbg((tb)->nhi->dev, fmt, ## arg)
#define __TB_SW_PRINT(level, sw, fmt, arg...) \
do { \
@@ -793,6 +793,7 @@ 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);
int tb_domain_disconnect_all_paths(struct tb *tb);
int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
static inline struct tb *tb_domain_get(struct tb *tb)
{
@@ -1263,6 +1264,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
const uuid_t *remote_uuid);
void tb_xdomain_add(struct tb_xdomain *xd);
void tb_xdomain_remove(struct tb_xdomain *xd);
void tb_xdomain_unregister(struct tb_xdomain *xd);
struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
u8 depth);
@@ -1479,6 +1481,7 @@ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw);
int usb4_dp_port_requested_bandwidth(struct tb_port *port);
int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable);
int usb4_pci_port_ltssm_state(struct tb_port *port);
static inline bool tb_is_usb4_port_device(const struct device *dev)
{
@@ -1556,4 +1559,12 @@ static inline void tb_retimer_debugfs_init(struct tb_retimer *rt) { }
static inline void tb_retimer_debugfs_remove(struct tb_retimer *rt) { }
#endif
#if IS_REACHABLE(CONFIG_CONFIGFS_FS)
int tb_configfs_init(void);
void tb_configfs_exit(void);
#else
static inline int tb_configfs_init(void) { return 0; }
static inline void tb_configfs_exit(void) { }
#endif
#endif

View File

@@ -182,8 +182,7 @@ struct tb_regs_switch_header {
/* DWORD 4 */
u32 plug_events_delay:8; /*
* RW, pause between plug events in
* milliseconds. Writing 0x00 is interpreted
* as 255ms.
* milliseconds.
*/
u32 cmuv:8;
u32 __unknown4:8;
@@ -216,6 +215,7 @@ struct tb_regs_switch_header {
#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
@@ -473,10 +473,25 @@ struct tb_regs_port_header {
/* 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)

View File

@@ -1661,6 +1661,7 @@ static void tb_test_tunnel_3dp(struct kunit *test)
KUNIT_ASSERT_EQ(test, tunnel3->npaths, 3);
KUNIT_ASSERT_EQ(test, tunnel3->paths[0]->path_length, 3);
tb_tunnel_put(tunnel3);
tb_tunnel_put(tunnel2);
tb_tunnel_put(tunnel1);
}
@@ -2852,7 +2853,252 @@ static void tb_test_property_copy(struct kunit *test)
tb_property_free_dir(src);
}
/*
* Reproducers for three memory-safety defects in
* drivers/thunderbolt/property.c reached from a crafted XDomain
* PROPERTIES_RESPONSE payload. Without the fix these trip KASAN or
* smash the kernel stack; with the fix each returns NULL cleanly.
*
* The on-wire entry layout matches struct tb_property_entry in
* property.c (private to that translation unit): u32 key_hi, u32
* key_lo, then a packed u32 = (type << 24) | (reserved << 16) |
* length, then u32 value. Each entry is 4 dwords.
*/
static void tb_test_property_parse_u32_wrap(struct kunit *test)
{
/*
* 0x102 dwords: enough for the entry's length field (0x100) to
* pass the "entry->length > block_len" gate so the wrap check
* is actually exercised. parse_dwdata's downstream OOB read
* lands ~16 GiB past the allocation regardless.
*/
u32 *block = kunit_kzalloc(test, 0x102 * sizeof(u32), GFP_KERNEL);
struct tb_property_dir *dir;
KUNIT_ASSERT_NOT_NULL(test, block);
block[0] = 0x55584401; /* "UXD" v1 magic */
block[1] = 0x00000004; /* Root directory length: one entry */
/*
* DATA entry whose value 0xffffff00 + length 0x100 wrap to 0
* in u32, passing the sum <= block_len guard even though the
* real offset is far past the allocation.
*/
block[2] = 0x61616161; /* key_hi */
block[3] = 0x61616161; /* key_lo */
block[4] = 0x64000100; /* type=DATA, reserved=0, length=0x100 */
block[5] = 0xffffff00; /* value */
dir = tb_property_parse_dir(block, 0x102);
KUNIT_EXPECT_NULL(test, dir);
tb_property_free_dir(dir);
}
static void tb_test_property_parse_recursion(struct kunit *test)
{
/*
* 10 dwords: rootdir header (2) + parent DIRECTORY entry (4) +
* the child entry that lives at dir_offset(2) + UUID(4) = 6,
* occupying block[6..9]. Each recursive level re-reads the
* same block[6..9] as its first child entry, which is itself
* a DIRECTORY pointing at offset 2.
*/
u32 *block = kunit_kzalloc(test, 10 * sizeof(u32), GFP_KERNEL);
struct tb_property_dir *dir;
KUNIT_ASSERT_NOT_NULL(test, block);
block[0] = 0x55584401; /* "UXD" v1 magic */
block[1] = 0x00000004; /* Root directory length: one entry */
/*
* DIRECTORY entry pointing at dir_offset = 2 with length = 8.
* Non-root parse derives content_offset = 6, content_len = 4,
* nentries = 1. block[6..9] is read both as the parent's UUID
* (kmemdup'd into dir->uuid) and as the single child entry --
* which is itself a DIRECTORY pointing at offset 2, so the
* recursion never terminates and the kernel stack is exhausted.
*/
block[2] = 0x61616161; /* key_hi */
block[3] = 0x61616161; /* key_lo */
block[4] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */
block[5] = 0x00000002; /* value = dir_offset */
block[6] = 0x62626262; /* doubles as UUID dword 0 / child key_hi */
block[7] = 0x62626262; /* doubles as UUID dword 1 / child key_lo */
block[8] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */
block[9] = 0x00000002; /* value = dir_offset (back at parent) */
dir = tb_property_parse_dir(block, 10);
KUNIT_EXPECT_NULL(test, dir);
tb_property_free_dir(dir);
}
static void tb_test_property_parse_dir_len_underflow(struct kunit *test)
{
/*
* Allocate exactly 7 dwords (28 bytes) so the kmalloc-32 chunk
* leaves a 4-byte slab redzone tail that KASAN-Generic can flag.
* With block_len = 7, dir_offset = 4, dir_len = 3, the non-root
* UUID kmemdup reads 16 bytes from byte 16, so bytes 28..31 fall
* in the redzone and trip a KASAN slab-out-of-bounds report on
* the pre-fix kernel. Sizing the buffer at a power of two (32,
* 64, ...) puts the over-read into the slab cache tail where
* KASAN's generic shadow does not flag it, and the test reduces
* to the downstream content_len = dir_len - 4 underflow path
* which also returns NULL.
*/
u32 *block = kunit_kzalloc(test, 7 * sizeof(u32), GFP_KERNEL);
struct tb_property_dir *dir;
KUNIT_ASSERT_NOT_NULL(test, block);
block[0] = 0x55584401; /* "UXD" v1 magic */
block[1] = 0x00000004; /* Root directory length: one entry */
/*
* DIRECTORY entry with length = 3 pointing at dir_offset = 4.
* tb_property_entry_valid() permits value(4) + length(3) <=
* block_len(7). Non-root parse begins with a kmemdup of 4
* dwords from dir_offset for the UUID; that read runs past the
* 28-byte allocation before the dir_len < 4 reject would fire.
*/
block[2] = 0x61616161; /* key_hi */
block[3] = 0x61616161; /* key_lo */
block[4] = 0x44000003; /* type=DIRECTORY, reserved=0, length=3 */
block[5] = 0x00000004; /* value = dir_offset */
/* block[6] is the start of the four UUID dwords; block[7..] is OOB. */
dir = tb_property_parse_dir(block, 7);
KUNIT_EXPECT_NULL(test, dir);
tb_property_free_dir(dir);
}
static void tb_test_property_parse_zero_length(struct kunit *test)
{
u32 *block = kunit_kzalloc(test, 6 * sizeof(u32), GFP_KERNEL);
struct tb_property_dir *dir;
KUNIT_ASSERT_NOT_NULL(test, block);
block[0] = 0x55584401; /* rootdir magic */
block[1] = 0x00000004; /* root length: one entry */
block[2] = 0x61616161; /* key_hi */
block[3] = 0x61616161; /* key_lo */
block[4] = 0x74000000; /* type=TEXT, reserved=0, length=0 */
block[5] = 0x00000000; /* value */
dir = tb_property_parse_dir(block, 6);
KUNIT_EXPECT_NULL(test, dir);
tb_property_free_dir(dir);
}
static void tb_test_property_parse_rootdir_overflow(struct kunit *test)
{
u32 *block = kunit_kzalloc(test, 4 * sizeof(u32), GFP_KERNEL);
struct tb_property_dir *dir;
KUNIT_ASSERT_NOT_NULL(test, block);
block[0] = 0x55584401; /* rootdir magic */
block[1] = 0x00000004; /* root length claims 4 dwords of content */
block[2] = 0x61616161;
block[3] = 0x61616161;
/* content_offset(2) + content_len(4) = 6 > block_len(4) */
dir = tb_property_parse_dir(block, 4);
KUNIT_EXPECT_NULL(test, dir);
tb_property_free_dir(dir);
}
static void tb_test_property_merge(struct kunit *test)
{
struct tb_property_dir *dir1, *dir2, *dir3;
struct tb_property *p;
uuid_t uuid;
int ret;
dir1 = tb_property_create_dir(&network_dir_uuid);
KUNIT_ASSERT_NOT_NULL(test, dir1);
ret = tb_property_add_immediate(dir1, "prtcid", 1);
KUNIT_EXPECT_EQ(test, ret, 0);
ret = tb_property_add_immediate(dir1, "prtcvers", 1);
KUNIT_EXPECT_EQ(test, ret, 0);
ret = tb_property_add_immediate(dir1, "prtcrevs", 0);
KUNIT_EXPECT_EQ(test, ret, 0);
ret = tb_property_add_immediate(dir1, "prtcstns", 0);
KUNIT_EXPECT_EQ(test, ret, 0);
dir2 = tb_property_create_dir(&network_dir_uuid);
KUNIT_ASSERT_NOT_NULL(test, dir2);
ret = tb_property_add_text(dir2, "descr", "This is text");
KUNIT_EXPECT_EQ(test, ret, 0);
/* This replaces the value in dir1 */
ret = tb_property_add_immediate(dir2, "prtcvers", 0x1234);
KUNIT_EXPECT_EQ(test, ret, 0);
uuid_gen(&uuid);
dir3 = tb_property_create_dir(&uuid);
KUNIT_ASSERT_NOT_NULL(test, dir3);
ret = tb_property_add_immediate(dir3, "value0", 0);
KUNIT_EXPECT_EQ(test, ret, 0);
ret = tb_property_add_text(dir3, "value1", "Text value");
KUNIT_EXPECT_EQ(test, ret, 0);
ret = tb_property_add_dir(dir2, "my", dir3);
KUNIT_EXPECT_EQ(test, ret, 0);
ret = tb_property_merge_dir(dir1, dir2, true);
KUNIT_EXPECT_EQ(test, ret, 0);
p = tb_property_get_next(dir1, NULL);
KUNIT_ASSERT_NOT_NULL(test, p);
KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcid");
KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
KUNIT_ASSERT_EQ(test, p->length, 1);
KUNIT_ASSERT_EQ(test, p->value.immediate, 1);
p = tb_property_get_next(dir1, p);
KUNIT_ASSERT_NOT_NULL(test, p);
KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcvers");
KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
KUNIT_ASSERT_EQ(test, p->length, 1);
KUNIT_ASSERT_EQ(test, p->value.immediate, 0x1234);
p = tb_property_get_next(dir1, p);
KUNIT_ASSERT_NOT_NULL(test, p);
KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcrevs");
KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
KUNIT_ASSERT_EQ(test, p->length, 1);
KUNIT_ASSERT_EQ(test, p->value.immediate, 0);
p = tb_property_get_next(dir1, p);
KUNIT_ASSERT_NOT_NULL(test, p);
KUNIT_ASSERT_STREQ(test, &p->key[0], "prtcstns");
KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_VALUE);
KUNIT_ASSERT_EQ(test, p->length, 1);
KUNIT_ASSERT_EQ(test, p->value.immediate, 0);
p = tb_property_get_next(dir1, p);
KUNIT_ASSERT_NOT_NULL(test, p);
KUNIT_ASSERT_STREQ(test, &p->key[0], "descr");
KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_TEXT);
KUNIT_ASSERT_EQ(test, p->length, 4);
KUNIT_ASSERT_STREQ(test, p->value.text, "This is text");
p = tb_property_get_next(dir1, p);
KUNIT_ASSERT_NOT_NULL(test, p);
KUNIT_ASSERT_STREQ(test, &p->key[0], "my");
KUNIT_ASSERT_EQ(test, p->type, TB_PROPERTY_TYPE_DIRECTORY);
compare_dirs(test, p->value.dir, dir3);
p = tb_property_get_next(dir1, p);
KUNIT_ASSERT_NULL(test, p);
tb_property_free_dir(dir2);
tb_property_free_dir(dir1);
}
static struct kunit_case tb_test_cases[] = {
KUNIT_CASE(tb_test_property_parse_u32_wrap),
KUNIT_CASE(tb_test_property_parse_recursion),
KUNIT_CASE(tb_test_property_parse_dir_len_underflow),
KUNIT_CASE(tb_test_path_basic),
KUNIT_CASE(tb_test_path_not_connected_walk),
KUNIT_CASE(tb_test_path_single_hop_walk),
@@ -2892,6 +3138,9 @@ static struct kunit_case tb_test_cases[] = {
KUNIT_CASE(tb_test_property_parse),
KUNIT_CASE(tb_test_property_format),
KUNIT_CASE(tb_test_property_copy),
KUNIT_CASE(tb_test_property_parse_zero_length),
KUNIT_CASE(tb_test_property_parse_rootdir_overflow),
KUNIT_CASE(tb_test_property_merge),
{ }
};

View File

@@ -290,6 +290,40 @@ static inline void tb_tunnel_changed(struct tb_tunnel *tunnel)
tunnel->src_port, tunnel->dst_port);
}
static int tb_pci_port_ltssm_state_detect(struct tb_port *port)
{
ktime_t timeout = ktime_add_ms(ktime_get(), 500);
do {
int ret;
ret = usb4_pci_port_ltssm_state(port);
if (ret < 0)
return ret;
if (ret == USB4_PCIE_LTSSM_DETECT)
return 0;
fsleep(50);
} while (ktime_before(ktime_get(), timeout));
return -ETIMEDOUT;
}
static int tb_pci_pre_activate(struct tb_tunnel *tunnel)
{
struct tb_port *down = tunnel->src_port;
struct tb_port *up = tunnel->dst_port;
int ret;
ret = tb_switch_is_usb4(down->sw) ?
tb_pci_port_ltssm_state_detect(down) : 0;
if (ret)
return ret;
return tb_switch_is_usb4(up->sw) ?
tb_pci_port_ltssm_state_detect(up) : 0;
}
static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable)
{
struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw);
@@ -505,6 +539,7 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
if (!tunnel)
return NULL;
tunnel->pre_activate = tb_pci_pre_activate;
tunnel->activate = tb_pci_activate;
tunnel->src_port = down;
tunnel->dst_port = up;

View File

@@ -294,7 +294,12 @@ int usb4_switch_setup(struct tb_switch *sw)
/* TBT3 supported by the CM */
val &= ~ROUTER_CS_5_CNS;
return tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
if (ret)
return ret;
return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_RR,
ROUTER_CS_6_RR, 500);
}
/**
@@ -304,7 +309,7 @@ int usb4_switch_setup(struct tb_switch *sw)
* Sets configuration valid bit for the router. Must be called before
* any tunnels can be set through the router and after
* usb4_switch_setup() has been called. Can be called to host and device
* routers (does nothing for the latter).
* routers (does nothing for the former).
*
* Return: %0 on success, negative errno otherwise.
*/
@@ -327,7 +332,7 @@ int usb4_switch_configuration_valid(struct tb_switch *sw)
return ret;
return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR,
ROUTER_CS_6_CR, 50);
ROUTER_CS_6_CR, 500);
}
/**
@@ -3145,3 +3150,27 @@ int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable)
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_adap + ADP_PCIE_CS_1, 1);
}
/**
* usb4_pci_port_ltssm_state() - Read PCIe adapter LTSSM state
* @port: PCIe adapter
*
* Return:
* * LTSSM state of @port.
* * Negative errno - On failure.
*/
int usb4_pci_port_ltssm_state(struct tb_port *port)
{
u32 val;
int ret;
if (!tb_port_is_pcie_down(port) && !tb_port_is_pcie_up(port))
return -EINVAL;
ret = tb_port_read(port, &val, TB_CFG_PORT,
port->cap_adap + ADP_PCIE_CS_0, 1);
if (ret)
return ret;
return FIELD_GET(ADP_PCIE_CS_0_LTSSM_MASK, val);
}

View File

@@ -138,7 +138,7 @@ bool usb4_usb3_port_match(struct device *usb4_port_dev,
return false;
/* Check if USB3 fwnode references same NHI where USB4 port resides */
if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode))
if (!device_match_fwnode(nhi->dev, nhi_fwnode))
return false;
if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num))

View File

@@ -136,6 +136,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
size_t size, enum tb_cfg_pkg_type type)
{
struct tb_cfg_request *req;
int ret;
req = tb_cfg_request_alloc();
if (!req)
@@ -147,7 +148,11 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response,
req->request_size = size;
req->request_type = type;
return tb_cfg_request(ctl, req, response_ready, req);
ret = tb_cfg_request(ctl, req, response_ready, req);
if (ret)
tb_cfg_request_put(req);
return ret;
}
/**
@@ -535,29 +540,19 @@ static int tb_xdp_link_state_status_request(struct tb_ctl *ctl, u64 route,
}
static int tb_xdp_link_state_status_response(struct tb *tb, struct tb_ctl *ctl,
struct tb_xdomain *xd, u8 sequence)
struct tb_xdomain *xd, u8 sequence,
u8 slw, u8 sls, u8 tls, u8 tlw)
{
struct tb_xdp_link_state_status_response res;
struct tb_port *port = tb_xdomain_downstream_port(xd);
u32 val[2];
int ret;
memset(&res, 0, sizeof(res));
tb_xdp_fill_header(&res.hdr, xd->route, sequence,
LINK_STATE_STATUS_RESPONSE, sizeof(res));
ret = tb_port_read(port, val, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_0, ARRAY_SIZE(val));
if (ret)
return ret;
res.slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
res.sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >>
LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT;
res.tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK;
res.tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >>
LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
res.slw = slw;
res.sls = sls;
res.tls = tls;
res.tlw = tlw;
return __tb_xdomain_response(ctl, &res, sizeof(res),
TB_CFG_PKG_XDOMAIN_RESP);
@@ -645,6 +640,32 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler)
}
EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler);
static int update_service_properties(struct device *dev, void *data)
{
struct tb_property_dir *root = data;
struct tb_service *svc;
struct tb_property *p;
svc = tb_to_service(dev);
if (!svc)
return 0;
guard(mutex)(&svc->lock);
/*
* Replace the static service properties with the dynamic one.
* Typically this is the same but service drivers can add their
* own dynamic properties here too.
*/
p = tb_property_find(root, svc->key, TB_PROPERTY_TYPE_DIRECTORY);
if (!p)
return 0;
if (svc->local_properties)
return tb_property_merge_dir(p->value.dir,
svc->local_properties, false);
return 0;
}
static void update_property_block(struct tb_xdomain *xd)
{
mutex_lock(&xdomain_lock);
@@ -669,6 +690,9 @@ static void update_property_block(struct tb_xdomain *xd)
tb_property_add_text(dir, "deviceid", utsname()->nodename);
tb_property_add_immediate(dir, "maxhopid", xd->local_max_hopid);
/* Add service specific dynamic properties */
device_for_each_child(&xd->dev, dir, update_service_properties);
ret = tb_property_format_dir(dir, NULL, 0);
if (ret < 0) {
dev_warn(&xd->dev, "local property block creation failed\n");
@@ -745,7 +769,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
mutex_lock(&tb->lock);
if (tb->root_switch)
uuid = tb->root_switch->uuid;
uuid = kmemdup(tb->root_switch->uuid, sizeof(*uuid), GFP_KERNEL);
else
uuid = NULL;
mutex_unlock(&tb->lock);
@@ -779,9 +803,13 @@ static void tb_xdp_handle_request(struct work_struct *work)
* the xdomain related to this connection as well in
* case there is a change in services it offers.
*/
if (xd && device_is_registered(&xd->dev))
queue_delayed_work(tb->wq, &xd->state_work,
msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
if (xd) {
mutex_lock(&xd->lock);
if (!xd->removing && device_is_registered(&xd->dev))
queue_delayed_work(tb->wq, &xd->state_work,
msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
mutex_unlock(&xd->lock);
}
break;
case UUID_REQUEST_OLD:
@@ -794,8 +822,12 @@ static void tb_xdp_handle_request(struct work_struct *work)
* received UUID request from the remote host.
*/
if (!ret && xd && xd->state == XDOMAIN_STATE_ERROR) {
dev_dbg(&xd->dev, "restarting handshake\n");
start_handshake(xd);
mutex_lock(&xd->lock);
if (!xd->removing) {
dev_dbg(&xd->dev, "restarting handshake\n");
start_handshake(xd);
}
mutex_unlock(&xd->lock);
}
break;
@@ -804,8 +836,47 @@ static void tb_xdp_handle_request(struct work_struct *work)
route);
if (xd) {
ret = tb_xdp_link_state_status_response(tb, ctl, xd,
sequence);
struct tb_port *port = tb_xdomain_downstream_port(xd);
u8 slw, sls, tls, tlw;
u32 val[2];
/*
* Read the adapter supported and target widths
* and speeds.
*/
ret = tb_port_read(port, val, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_0,
ARRAY_SIZE(val));
if (ret)
break;
slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >>
LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT;
tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK;
tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >>
LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
/*
* When we have higher UUID, we are supposed to
* return ERROR_NOT_READY if the tlw is not yet
* set according to the Inter-Domain spec for
* USB4 v2.
*/
if (xd->state == XDOMAIN_STATE_BONDING_UUID_HIGH &&
xd->target_link_width &&
xd->target_link_width != tlw) {
tb_dbg(tb, "%llx: target link width not yet set %#x != %#x\n",
route, tlw, xd->target_link_width);
tb_xdp_error_response(ctl, route, sequence,
ERROR_NOT_READY);
} else {
tb_dbg(tb, "%llx: replying with target link width set to %#x\n",
route, tlw);
ret = tb_xdp_link_state_status_response(tb, ctl,
xd, sequence, slw, sls, tls, tlw);
}
} else {
tb_xdp_error_response(ctl, route, sequence,
ERROR_NOT_READY);
@@ -822,9 +893,13 @@ static void tb_xdp_handle_request(struct work_struct *work)
ret = tb_xdp_link_state_change_response(ctl, route,
sequence, 0);
xd->target_link_width = lsc->tlw;
queue_delayed_work(tb->wq, &xd->state_work,
msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
mutex_lock(&xd->lock);
if (!xd->removing) {
xd->target_link_width = lsc->tlw;
queue_delayed_work(tb->wq, &xd->state_work,
msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT));
}
mutex_unlock(&xd->lock);
} else {
tb_xdp_error_response(ctl, route, sequence,
ERROR_NOT_READY);
@@ -846,6 +921,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
}
out:
kfree(uuid);
kfree(xw->pkg);
kfree(xw);
@@ -901,6 +977,44 @@ void tb_unregister_service_driver(struct tb_service_driver *drv)
}
EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
static int update_xdomain(struct device *dev, void *data)
{
struct tb_xdomain *xd;
xd = tb_to_xdomain(dev);
if (xd) {
mutex_lock(&xd->lock);
if (!xd->removing)
queue_delayed_work(xd->tb->wq,
&xd->properties_changed_work,
msecs_to_jiffies(50));
mutex_unlock(&xd->lock);
}
return 0;
}
/**
* tb_service_properties_changed() - Notify the other host about changes
* @svc: Service whose properties changed
*
* Notifies the other host that service properties may have been
* changed. This should be called whenever @svc->local_properties is
* updated.
*/
void tb_service_properties_changed(struct tb_service *svc)
{
struct tb_xdomain *xd = tb_service_parent(svc);
if (xd->is_unplugged)
return;
scoped_guard(mutex, &xdomain_lock)
xdomain_property_block_gen++;
update_xdomain(&xd->dev, NULL);
}
EXPORT_SYMBOL_GPL(tb_service_properties_changed);
static ssize_t key_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -1000,10 +1114,11 @@ static void tb_service_release(struct device *dev)
struct tb_service *svc = container_of(dev, struct tb_service, dev);
struct tb_xdomain *xd = tb_service_parent(svc);
tb_service_debugfs_remove(svc);
tb_property_free_dir(svc->remote_properties);
ida_free(&xd->service_ids, svc->id);
kfree(svc->key);
kfree(svc);
tb_xdomain_put(xd);
}
const struct device_type tb_service_type = {
@@ -1014,6 +1129,24 @@ const struct device_type tb_service_type = {
};
EXPORT_SYMBOL_GPL(tb_service_type);
static void update_service(struct tb_service *svc, struct tb_property *property)
{
struct tb_property_dir *dir = property->value.dir;
guard(mutex)(&svc->lock);
tb_property_free_dir(svc->remote_properties);
svc->remote_properties = tb_property_copy_dir(dir);
kobject_uevent(&svc->dev.kobj, KOBJ_CHANGE);
}
static void __unregister_service(struct device *dev)
{
struct tb_service *svc = tb_to_service(dev);
tb_service_debugfs_remove(svc);
device_unregister(&svc->dev);
}
static int remove_missing_service(struct device *dev, void *data)
{
struct tb_xdomain *xd = data;
@@ -1025,7 +1158,7 @@ static int remove_missing_service(struct device *dev, void *data)
if (!tb_property_find(xd->remote_properties, svc->key,
TB_PROPERTY_TYPE_DIRECTORY))
device_unregister(dev);
__unregister_service(dev);
return 0;
}
@@ -1066,6 +1199,12 @@ static int populate_service(struct tb_service *svc,
if (!svc->key)
return -ENOMEM;
svc->remote_properties = tb_property_copy_dir(dir);
if (!svc->remote_properties) {
kfree(svc->key);
return -ENOMEM;
}
return 0;
}
@@ -1090,6 +1229,7 @@ static void enumerate_services(struct tb_xdomain *xd)
/* If the service exists already we are fine */
dev = device_find_child(&xd->dev, p, find_service);
if (dev) {
update_service(tb_to_service(dev), p);
put_device(dev);
continue;
}
@@ -1112,12 +1252,14 @@ static void enumerate_services(struct tb_xdomain *xd)
svc->id = id;
svc->dev.bus = &tb_bus_type;
svc->dev.type = &tb_service_type;
svc->dev.parent = &xd->dev;
svc->dev.parent = get_device(&xd->dev);
mutex_init(&svc->lock);
dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
tb_service_debugfs_init(svc);
if (device_register(&svc->dev)) {
tb_service_debugfs_remove(svc);
put_device(&svc->dev);
break;
}
@@ -1931,7 +2073,13 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd)
if (tb_port_get_link_generation(down) >= 4) {
down->bonded = false;
down->dual_link_port->bonded = false;
} else if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
return;
}
if (!xd->bonding_possible)
return;
if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
/*
* Just return port structures back to way they were and
* update credits. No need to update userspace because
@@ -1988,6 +2136,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
INIT_DELAYED_WORK(&xd->state_work, tb_xdomain_state_work);
INIT_DELAYED_WORK(&xd->properties_changed_work,
tb_xdomain_properties_changed);
atomic_set(&xd->ntunnels, 0);
xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t), GFP_KERNEL);
if (!xd->local_uuid)
@@ -2050,43 +2199,61 @@ void tb_xdomain_add(struct tb_xdomain *xd)
static int unregister_service(struct device *dev, void *data)
{
device_unregister(dev);
__unregister_service(dev);
return 0;
}
/**
* tb_xdomain_remove() - Remove XDomain from the bus
* tb_xdomain_remove() - Remove XDomain
* @xd: XDomain to remove
*
* This will stop all ongoing configuration work and remove the XDomain
* along with any services from the bus. When the last reference to @xd
* is released the object will be released as well.
* This will stop all ongoing configuration work. XDomain is not removed
* from the bus if it was added. That needs to be done separately by
* calling tb_xdomain_unregister().
*
* Called with @tb->lock held.
*/
void tb_xdomain_remove(struct tb_xdomain *xd)
{
tb_xdomain_debugfs_remove(xd);
mutex_lock(&xd->lock);
xd->removing = true;
mutex_unlock(&xd->lock);
stop_handshake(xd);
tb_xdomain_link_exit(xd);
if (!device_is_registered(&xd->dev)) {
/*
* Undo runtime PM here explicitly because it is
* possible that the XDomain was never added to the bus
* and thus device_del() is not called for it
* (device_del() would handle this otherwise).
*/
pm_runtime_disable(&xd->dev);
pm_runtime_put_noidle(&xd->dev);
pm_runtime_set_suspended(&xd->dev);
put_device(&xd->dev);
}
}
/**
* tb_xdomain_unregister() - Unregister XDomain
* @xd: XDomain to unregister
*
* This will unregister the XDomain along with any services from the
* bus. When the last reference to @xd is released the object will be
* released as well.
*/
void tb_xdomain_unregister(struct tb_xdomain *xd)
{
lockdep_assert_not_held(&xd->tb->lock);
device_for_each_child_reverse(&xd->dev, xd, unregister_service);
tb_xdomain_link_exit(xd);
/*
* Undo runtime PM here explicitly because it is possible that
* the XDomain was never added to the bus and thus device_del()
* is not called for it (device_del() would handle this otherwise).
*/
pm_runtime_disable(&xd->dev);
pm_runtime_put_noidle(&xd->dev);
pm_runtime_set_suspended(&xd->dev);
if (!device_is_registered(&xd->dev)) {
put_device(&xd->dev);
} else {
dev_info(&xd->dev, "host disconnected\n");
device_unregister(&xd->dev);
}
dev_info(&xd->dev, "host disconnected\n");
device_unregister(&xd->dev);
}
/**
@@ -2265,9 +2432,15 @@ int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring)
{
return tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
transmit_ring, receive_path,
receive_ring);
int ret;
ret = tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
transmit_ring, receive_path,
receive_ring);
if (ret)
return ret;
atomic_inc(&xd->ntunnels);
return 0;
}
EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
@@ -2290,9 +2463,15 @@ int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring)
{
return tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
transmit_ring, receive_path,
receive_ring);
int ret;
ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
transmit_ring, receive_path,
receive_ring);
if (ret)
return ret;
atomic_dec(&xd->ntunnels);
return 0;
}
EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
@@ -2308,6 +2487,9 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
{
struct tb_port *port;
if (!sw)
return NULL;
tb_switch_for_each_port(sw, port) {
struct tb_xdomain *xd;
@@ -2470,19 +2652,6 @@ bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type,
return ret > 0;
}
static int update_xdomain(struct device *dev, void *data)
{
struct tb_xdomain *xd;
xd = tb_to_xdomain(dev);
if (xd) {
queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
msecs_to_jiffies(50));
}
return 0;
}
static void update_all_xdomains(void)
{
bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);

View File

@@ -13,6 +13,7 @@
#include <linux/types.h>
struct config_group;
struct fwnode_handle;
struct device;
@@ -153,6 +154,9 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block,
ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
size_t block_len);
struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir);
int tb_property_merge_dir(struct tb_property_dir *parent,
const struct tb_property_dir *dir,
bool replace);
struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid);
void tb_property_free_dir(struct tb_property_dir *dir);
int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
@@ -209,6 +213,8 @@ enum tb_link_width {
* @link_width: Width of the downstream facing link
* @link_usb4: Downstream link is USB4
* @is_unplugged: The XDomain is unplugged
* @removing: Set by tb_xdomain_remove() under @lock to prevent
* concurrent delayed work queueing
* @needs_uuid: If the XDomain does not have @remote_uuid it will be
* queried first
* @service_ids: Used to generate IDs for the services
@@ -228,6 +234,7 @@ enum tb_link_width {
* changed notification
* @bonding_possible: True if lane bonding is possible on local side
* @target_link_width: Target link width from the remote host
* @ntunnels: Keeps track of how many tunnels go through this XDomain
* @link: Root switch link the remote domain is connected (ICM only)
* @depth: Depth in the chain the remote domain is connected (ICM only)
*
@@ -257,6 +264,7 @@ struct tb_xdomain {
enum tb_link_width link_width;
bool link_usb4;
bool is_unplugged;
bool removing;
bool needs_uuid;
struct ida service_ids;
struct ida in_hopids;
@@ -273,6 +281,7 @@ struct tb_xdomain {
int properties_changed_retries;
bool bonding_possible;
u8 target_link_width;
atomic_t ntunnels;
u8 link;
u8 depth;
};
@@ -392,6 +401,10 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler);
* @prtcvers: Protocol version from the properties directory
* @prtcrevs: Protocol software revision from the properties directory
* @prtcstns: Protocol settings mask from the properties directory
* @lock: Protects this structure
* @local_properties: Properties owned by the service driver
* @remote_properties: Properties read from the remote service. These
* are read-only.
* @debugfs_dir: Pointer to the service debugfs directory. Always created
* when debugfs is enabled. Can be used by service drivers to
* add their own entries under the service.
@@ -399,6 +412,9 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler);
* Each domain exposes set of services it supports as collection of
* properties. For each service there will be one corresponding
* &struct tb_service. Service drivers are bound to these.
*
* Service drivers can add their own dynamic properties to
* @local_properties but whenever they do so @lock must be held.
*/
struct tb_service {
struct device dev;
@@ -408,6 +424,9 @@ struct tb_service {
u32 prtcvers;
u32 prtcrevs;
u32 prtcstns;
struct mutex lock;
struct tb_property_dir *local_properties;
struct tb_property_dir *remote_properties;
struct dentry *debugfs_dir;
};
@@ -476,16 +495,17 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
return tb_to_xdomain(svc->dev.parent);
}
void tb_service_properties_changed(struct tb_service *svc);
/**
* struct tb_nhi - thunderbolt native host interface
* @lock: Must be held during ring creation/destruction. Is acquired by
* interrupt_work when dispatching interrupts to individual rings.
* @pdev: Pointer to the PCI device
* @dev: Device associated with this NHI instance
* @ops: NHI specific optional ops
* @iobase: MMIO space of the NHI
* @tx_rings: All Tx rings available on this host controller
* @rx_rings: All Rx rings available on this host controller
* @msix_ida: Used to allocate MSI-X vectors for rings
* @going_away: The host controller device is about to disappear so when
* this flag is set, avoid touching the hardware anymore.
* @iommu_dma_protection: An IOMMU will isolate external-facing ports.
@@ -493,20 +513,21 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc)
* MSI-X is used.
* @hop_count: Number of rings (end point hops) supported by NHI.
* @quirks: NHI specific quirks if any
* @domain_released: Completed when domain has been fully released
*/
struct tb_nhi {
spinlock_t lock;
struct pci_dev *pdev;
struct device *dev;
const struct tb_nhi_ops *ops;
void __iomem *iobase;
struct tb_ring **tx_rings;
struct tb_ring **rx_rings;
struct ida msix_ida;
bool going_away;
bool iommu_dma_protection;
struct work_struct interrupt_work;
u32 hop_count;
unsigned long quirks;
struct completion domain_released;
};
/**
@@ -535,6 +556,9 @@ struct tb_nhi {
* @start_poll: Called when ring interrupt is triggered to start
* polling. Passing %NULL keeps the ring in interrupt mode.
* @poll_data: Data passed to @start_poll
* @interval_nsec: Interval counter if interrupt throttling is to be
* used with this ring (in ns)
* @wait: Used to signal that the ring may be empty now
*/
struct tb_ring {
spinlock_t lock;
@@ -558,6 +582,8 @@ struct tb_ring {
u16 eof_mask;
void (*start_poll)(void *data);
void *poll_data;
unsigned int interval_nsec;
wait_queue_head_t wait;
};
/* Leave ring interrupt enabled on suspend */
@@ -609,7 +635,20 @@ struct ring_frame {
};
/* Minimum size for ring_rx */
#define TB_FRAME_SIZE 0x100
#define TB_FRAME_SIZE 256
#define TB_MAX_FRAME_SIZE 4096
static inline size_t tb_ring_frame_size(const struct ring_frame *frame)
{
if (frame->size)
return frame->size;
return TB_MAX_FRAME_SIZE;
}
static inline size_t tb_ring_size(const struct tb_ring *ring)
{
return ring->size;
}
struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size,
unsigned int flags);
@@ -618,6 +657,7 @@ struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size,
u16 sof_mask, u16 eof_mask,
void (*start_poll)(void *), void *poll_data);
void tb_ring_start(struct tb_ring *ring);
bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec);
void tb_ring_stop(struct tb_ring *ring);
void tb_ring_free(struct tb_ring *ring);
@@ -670,6 +710,8 @@ static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame)
struct ring_frame *tb_ring_poll(struct tb_ring *ring);
void tb_ring_poll_complete(struct tb_ring *ring);
int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec);
/**
* tb_ring_dma_device() - Return device used for DMA mapping
* @ring: Ring whose DMA device is retrieved
@@ -681,12 +723,17 @@ void tb_ring_poll_complete(struct tb_ring *ring);
*/
static inline struct device *tb_ring_dma_device(struct tb_ring *ring)
{
return &ring->nhi->pdev->dev;
return ring->nhi->dev;
}
bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode);
#if IS_REACHABLE(CONFIG_CONFIGFS_FS)
int tb_configfs_register_group(struct config_group *group);
void tb_configfs_unregister_group(struct config_group *group);
#endif
#else /* CONFIG_USB4 */
static inline bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode)