mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
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:
83
Documentation/ABI/testing/configfs-thunderbolt_stream
Normal file
83
Documentation/ABI/testing/configfs-thunderbolt_stream
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
61
drivers/thunderbolt/configfs.c
Normal file
61
drivers/thunderbolt/configfs.c
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
622
drivers/thunderbolt/pci.c
Normal 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);
|
||||
@@ -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
1698
drivers/thunderbolt/stream.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user