diff options
| author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-06-12 11:44:24 +0300 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-06-12 11:44:24 +0300 |
| commit | f176a7c3e44f80b4493d22aa12d02673243ec7d8 (patch) | |
| tree | 8ca94801b69d603617663e13dfcc6432cdf5e4dc | |
| parent | 511e746700ee6e93104d061a87743906128ab709 (diff) | |
| parent | c1bef05763c94ae284ee2881c03bf0753f8d213a (diff) | |
| download | linux-f176a7c3e44f80b4493d22aa12d02673243ec7d8.tar.xz | |
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
...
31 files changed, 3710 insertions, 919 deletions
diff --git a/Documentation/ABI/testing/configfs-thunderbolt_stream b/Documentation/ABI/testing/configfs-thunderbolt_stream new file mode 100644 index 000000000000..7abc6b73a1e4 --- /dev/null +++ b/Documentation/ABI/testing/configfs-thunderbolt_stream @@ -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. diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 89df26553aa0..91a6cb109988 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -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 diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index 7aae5d915a1e..f8f97e8e2226 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -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; diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index db3b0bef48f4..294b3227a545 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -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 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index b44b32dcb832..beb054c3126b 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -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 diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 45d1415871b4..53546bc477a5 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -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); } diff --git a/drivers/thunderbolt/configfs.c b/drivers/thunderbolt/configfs.c new file mode 100644 index 000000000000..dc6bc363dfe8 --- /dev/null +++ b/drivers/thunderbolt/configfs.c @@ -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); +} diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index b2fd60fc7bcc..cd47b627f97b 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -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; diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 042f6a0d0f7f..f5cf0e177f40 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -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++) diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index b4aa79d482a0..7877319b1b03 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -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); } diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 317780b99992..479fa4d265c2 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -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(); } diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 5477b9437048..5681c17f82ec 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -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"); diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 9d95bf3ab44c..10fefac3b1d9 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -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; } diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 2bb2e79ca3cb..0f795ea58756 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -2,7 +2,7 @@ /* * Thunderbolt driver - NHI driver * - * The NHI (native host interface) is the pci device that allows us to send and + * The NHI (native host interface) is the device that allows us to send and * receive frames from the thunderbolt bus. * * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> @@ -12,7 +12,6 @@ #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> @@ -34,19 +33,9 @@ * transferred. */ #define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID -/* - * 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 #define NHI_MAILBOX_TIMEOUT 500 /* ms */ -/* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) - static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); @@ -93,7 +82,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) u32 old, new; if (ring->irq > 0) { - u32 step, shift, ivr, misc; + u32 step, shift, ivr, misc, itr; void __iomem *ivr_base; int auto_clear_bit; int index; @@ -131,6 +120,12 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) if (active) ivr |= ring->vector << shift; iowrite32(ivr, ivr_base + step); + + /* Throttling is specified in 256ns increments */ + itr = DIV_ROUND_UP(ring->interval_nsec, 256); + itr &= REG_INT_THROTTLING_RATE_INTERVAL_MASK; + iowrite32(itr, ring->nhi->iobase + REG_INT_THROTTLING_RATE + + ring->vector * 4); } old = ioread32(ring->nhi->iobase + reg); @@ -139,15 +134,14 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) else new = old & ~mask; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "%s interrupt at register %#x bit %d (%#x -> %#x)\n", active ? "enabling" : "disabling", reg, interrupt_bit, old, new); if (new == old) - dev_WARN(&ring->nhi->pdev->dev, - "interrupt for %s %d is already %s\n", - RING_TYPE(ring), ring->hop, - str_enabled_disabled(active)); + dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n", + RING_TYPE(ring), ring->hop, + str_enabled_disabled(active)); if (active) iowrite32(new, ring->nhi->iobase + reg); @@ -160,7 +154,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) * * Use only during init and shutdown. */ -static void nhi_disable_interrupts(struct tb_nhi *nhi) +void nhi_disable_interrupts(struct tb_nhi *nhi) { int i = 0; /* disable interrupts */ @@ -319,6 +313,8 @@ invoke_callback: if (frame->callback) frame->callback(ring, frame, canceled); } + + wake_up(&ring->wait); } int __tb_ring_enqueue(struct tb_ring *ring, struct ring_frame *frame) @@ -445,7 +441,7 @@ static void ring_clear_msix(const struct tb_ring *ring) 4 * (ring->nhi->hop_count / 32)); } -static irqreturn_t ring_msix(int irq, void *data) +irqreturn_t ring_msix(int irq, void *data) { struct tb_ring *ring = data; @@ -459,51 +455,6 @@ static irqreturn_t ring_msix(int irq, void *data) return IRQ_HANDLED; } -static int ring_request_msix(struct tb_ring *ring, bool no_suspend) -{ - struct tb_nhi *nhi = ring->nhi; - unsigned long irqflags; - int ret; - - if (!nhi->pdev->msix_enabled) - return 0; - - ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); - if (ret < 0) - return ret; - - ring->vector = ret; - - ret = pci_irq_vector(ring->nhi->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->msix_ida, ring->vector); - - return ret; -} - -static void ring_release_msix(struct tb_ring *ring) -{ - if (ring->irq <= 0) - return; - - free_irq(ring->irq, ring); - ida_free(&ring->nhi->msix_ida, ring->vector); - ring->vector = 0; - ring->irq = 0; -} - static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) { unsigned int start_hop = RING_FIRST_USABLE_HOPID; @@ -512,7 +463,7 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) if (nhi->quirks & QUIRK_E2E) { start_hop = RING_FIRST_USABLE_HOPID + 1; if (ring->flags & RING_FLAG_E2E && !ring->is_tx) { - dev_dbg(&nhi->pdev->dev, "quirking E2E TX HopID %u -> %u\n", + dev_dbg(nhi->dev, "quirking E2E TX HopID %u -> %u\n", ring->e2e_tx_hop, RING_E2E_RESERVED_HOPID); ring->e2e_tx_hop = RING_E2E_RESERVED_HOPID; } @@ -543,23 +494,23 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) } if (ring->hop > 0 && ring->hop < start_hop) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->hop < 0 || ring->hop >= nhi->hop_count) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->is_tx && nhi->tx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "TX hop %d already allocated\n", + dev_warn(nhi->dev, "TX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; } if (!ring->is_tx && nhi->rx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "RX hop %d already allocated\n", + dev_warn(nhi->dev, "RX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; @@ -584,7 +535,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, { struct tb_ring *ring = NULL; - dev_dbg(&nhi->pdev->dev, "allocating %s ring %d of size %d\n", + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", transmit ? "TX" : "RX", hop, size); ring = kzalloc_obj(*ring); @@ -595,6 +546,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, INIT_LIST_HEAD(&ring->queue); INIT_LIST_HEAD(&ring->in_flight); INIT_WORK(&ring->work, ring_work); + init_waitqueue_head(&ring->wait); ring->nhi = nhi; ring->hop = hop; @@ -610,14 +562,16 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->start_poll = start_poll; ring->poll_data = poll_data; - ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev, - size * sizeof(*ring->descriptors), - &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); + ring->descriptors = dma_alloc_coherent(ring->nhi->dev, + size * sizeof(*ring->descriptors), + &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); if (!ring->descriptors) goto err_free_ring; - if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND)) - goto err_free_descs; + if (nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } if (nhi_alloc_hop(nhi, ring)) goto err_release_msix; @@ -625,9 +579,10 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, return ring; err_release_msix: - ring_release_msix(ring); + if (nhi->ops->release_ring_irq) + nhi->ops->release_ring_irq(ring); err_free_descs: - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: @@ -694,10 +649,10 @@ void tb_ring_start(struct tb_ring *ring) if (ring->nhi->going_away) goto err; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "ring already started\n"); + dev_WARN(ring->nhi->dev, "ring already started\n"); goto err; } - dev_dbg(&ring->nhi->pdev->dev, "starting %s %d\n", + dev_dbg(ring->nhi->dev, "starting %s %d\n", RING_TYPE(ring), ring->hop); if (ring->flags & RING_FLAG_FRAME) { @@ -734,11 +689,11 @@ void tb_ring_start(struct tb_ring *ring) hop &= REG_RX_OPTIONS_E2E_HOP_MASK; flags |= hop; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d with TX HopID %d\n", RING_TYPE(ring), ring->hop, ring->e2e_tx_hop); } else { - dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d\n", + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d\n", RING_TYPE(ring), ring->hop); } @@ -754,6 +709,31 @@ err: } EXPORT_SYMBOL_GPL(tb_ring_start); +static bool tb_ring_empty(struct tb_ring *ring) +{ + guard(spinlock_irqsave)(&ring->lock); + return list_empty(&ring->in_flight); +} + +/** + * tb_ring_flush() - Waits for a ring to be empty + * @ring: Ring to wait + * @timeout_msec: Timeout in ms how long to wait. + * + * This can be called before stopping a ring to make sure all the frames + * submitted prior have been completed. + * + * Return: %true if the ring is empty now, %false otherwise. + */ +bool tb_ring_flush(struct tb_ring *ring, unsigned int timeout_msec) +{ + if (!wait_event_timeout(ring->wait, tb_ring_empty(ring), + msecs_to_jiffies(timeout_msec))) + return false; + return tb_ring_empty(ring); +} +EXPORT_SYMBOL_GPL(tb_ring_flush); + /** * tb_ring_stop() - shutdown a ring * @ring: Ring to stop @@ -772,12 +752,12 @@ void tb_ring_stop(struct tb_ring *ring) { spin_lock_irq(&ring->nhi->lock); spin_lock(&ring->lock); - dev_dbg(&ring->nhi->pdev->dev, "stopping %s %d\n", + dev_dbg(ring->nhi->dev, "stopping %s %d\n", RING_TYPE(ring), ring->hop); if (ring->nhi->going_away) goto err; if (!ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n", + dev_WARN(ring->nhi->dev, "%s %d already stopped\n", RING_TYPE(ring), ring->hop); goto err; } @@ -815,6 +795,8 @@ EXPORT_SYMBOL_GPL(tb_ring_stop); */ void tb_ring_free(struct tb_ring *ring) { + struct tb_nhi *nhi = ring->nhi; + spin_lock_irq(&ring->nhi->lock); /* * Dissociate the ring from the NHI. This also ensures that @@ -826,14 +808,15 @@ void tb_ring_free(struct tb_ring *ring) ring->nhi->rx_rings[ring->hop] = NULL; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n", + dev_WARN(ring->nhi->dev, "%s %d still running\n", RING_TYPE(ring), ring->hop); } spin_unlock_irq(&ring->nhi->lock); - ring_release_msix(ring); + if (nhi->ops->release_ring_irq) + nhi->ops->release_ring_irq(ring); - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); @@ -841,7 +824,7 @@ void tb_ring_free(struct tb_ring *ring) ring->descriptors_dma = 0; - dev_dbg(&ring->nhi->pdev->dev, "freeing %s %d\n", RING_TYPE(ring), + dev_dbg(ring->nhi->dev, "freeing %s %d\n", RING_TYPE(ring), ring->hop); /* @@ -855,6 +838,26 @@ void tb_ring_free(struct tb_ring *ring) EXPORT_SYMBOL_GPL(tb_ring_free); /** + * tb_ring_throttling() - Configure throttling for ring interrupt + * @ring: Ring to configure + * @interval_nsec: Interval counter for moderation (in ns), %0 disables + * + * Enables or disables ring interrupt throttling. The ring must be + * stopped for this to be called. Granularity is 256 ns. + * + * Return: %0 on success, negative errno otherwise. + */ +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec) +{ + guard(spinlock_irqsave)(&ring->lock); + if (WARN_ON_ONCE(ring->running)) + return -EBUSY; + ring->interval_nsec = interval_nsec; + return 0; +} +EXPORT_SYMBOL_GPL(tb_ring_throttling); + +/** * nhi_mailbox_cmd() - Send a command through NHI mailbox * @nhi: Pointer to the NHI structure * @cmd: Command to send @@ -912,7 +915,7 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi) return (enum nhi_fw_mode)val; } -static void nhi_interrupt_work(struct work_struct *work) +void nhi_interrupt_work(struct work_struct *work) { struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work); int value = 0; /* Suppress uninitialized usage warning. */ @@ -940,9 +943,7 @@ static void nhi_interrupt_work(struct work_struct *work) if ((value & (1 << (bit % 32))) == 0) continue; if (type == 2) { - dev_warn(&nhi->pdev->dev, - "RX overflow for ring %d\n", - hop); + dev_warn(nhi->dev, "RX overflow for ring %d\n", hop); continue; } if (type == 0) @@ -950,7 +951,7 @@ static void nhi_interrupt_work(struct work_struct *work) else ring = nhi->rx_rings[hop]; if (ring == NULL) { - dev_warn(&nhi->pdev->dev, + dev_warn(nhi->dev, "got interrupt for inactive %s ring %d\n", type ? "RX" : "TX", hop); @@ -964,7 +965,7 @@ static void nhi_interrupt_work(struct work_struct *work) spin_unlock_irq(&nhi->lock); } -static irqreturn_t nhi_msi(int irq, void *data) +irqreturn_t nhi_msi(int irq, void *data) { struct tb_nhi *nhi = data; schedule_work(&nhi->interrupt_work); @@ -973,8 +974,7 @@ static irqreturn_t nhi_msi(int irq, void *data) static int __nhi_suspend_noirq(struct device *dev, bool wakeup) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -982,7 +982,7 @@ static int __nhi_suspend_noirq(struct device *dev, bool wakeup) if (ret) return ret; - if (nhi->ops && nhi->ops->suspend_noirq) { + if (nhi->ops->suspend_noirq) { ret = nhi->ops->suspend_noirq(tb->nhi, wakeup); if (ret) return ret; @@ -998,21 +998,19 @@ static int nhi_suspend_noirq(struct device *dev) static int nhi_freeze_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_freeze_noirq(tb); } static int nhi_thaw_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_thaw_noirq(tb); } -static bool nhi_wake_supported(struct pci_dev *pdev) +static bool nhi_wake_supported(struct device *dev) { u8 val; @@ -1020,7 +1018,7 @@ static bool nhi_wake_supported(struct pci_dev *pdev) * If power rails are sustainable for wakeup from S4 this * property is set by the BIOS. */ - if (!device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val)) + if (!device_property_read_u8(dev, "WAKE_SUPPORTED", &val)) return !!val; return true; @@ -1028,33 +1026,15 @@ static bool nhi_wake_supported(struct pci_dev *pdev) static int nhi_poweroff_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); bool wakeup; - wakeup = device_may_wakeup(dev) && nhi_wake_supported(pdev); + wakeup = device_may_wakeup(dev) && nhi_wake_supported(dev); return __nhi_suspend_noirq(dev, wakeup); } -static void nhi_enable_int_throttling(struct tb_nhi *nhi) -{ - /* Throttling is specified in 256ns increments */ - u32 throttle = DIV_ROUND_UP(128 * NSEC_PER_USEC, 256); - unsigned int i; - - /* - * Configure interrupt throttling for all vectors even if we - * only use few. - */ - for (i = 0; i < MSIX_MAX_VECS; i++) { - u32 reg = REG_INT_THROTTLING_RATE + i * 4; - iowrite32(throttle, nhi->iobase + reg); - } -} - static int nhi_resume_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1063,15 +1043,12 @@ static int nhi_resume_noirq(struct device *dev) * unplugged last device which causes the host controller to go * away on PCs. */ - if (!pci_device_is_present(pdev)) { + if ((nhi->ops->is_present && !nhi->ops->is_present(nhi))) { nhi->going_away = true; - } else { - if (nhi->ops && nhi->ops->resume_noirq) { - ret = nhi->ops->resume_noirq(nhi); - if (ret) - return ret; - } - nhi_enable_int_throttling(tb->nhi); + } else if (nhi->ops->resume_noirq) { + ret = nhi->ops->resume_noirq(nhi); + if (ret) + return ret; } return tb_domain_resume_noirq(tb); @@ -1079,32 +1056,29 @@ static int nhi_resume_noirq(struct device *dev) static int nhi_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_suspend(tb); } static void nhi_complete(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); /* * If we were runtime suspended when system suspend started, * schedule runtime resume now. It should bring the domain back * to functional state. */ - if (pm_runtime_suspended(&pdev->dev)) - pm_runtime_resume(&pdev->dev); + if (pm_runtime_suspended(dev)) + pm_runtime_resume(dev); else tb_domain_complete(tb); } static int nhi_runtime_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1112,7 +1086,7 @@ static int nhi_runtime_suspend(struct device *dev) if (ret) return ret; - if (nhi->ops && nhi->ops->runtime_suspend) { + if (nhi->ops->runtime_suspend) { ret = nhi->ops->runtime_suspend(tb->nhi); if (ret) return ret; @@ -1122,115 +1096,39 @@ static int nhi_runtime_suspend(struct device *dev) static int nhi_runtime_resume(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; - if (nhi->ops && nhi->ops->runtime_resume) { + if (nhi->ops->runtime_resume) { ret = nhi->ops->runtime_resume(nhi); if (ret) return ret; } - nhi_enable_int_throttling(nhi); return tb_domain_runtime_resume(tb); } -static void nhi_shutdown(struct tb_nhi *nhi) +void nhi_shutdown(struct tb_nhi *nhi) { int i; - dev_dbg(&nhi->pdev->dev, "shutdown\n"); + dev_dbg(nhi->dev, "shutdown\n"); for (i = 0; i < nhi->hop_count; i++) { if (nhi->tx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "TX ring %d is still active\n", i); if (nhi->rx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "RX ring %d is still active\n", i); } nhi_disable_interrupts(nhi); - /* - * We have to release the irq before calling flush_work. Otherwise an - * already executing IRQ handler could call schedule_work again. - */ - if (!nhi->pdev->msix_enabled) { - devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi); - flush_work(&nhi->interrupt_work); - } - ida_destroy(&nhi->msix_ida); - if (nhi->ops && nhi->ops->shutdown) + if (nhi->ops->shutdown) nhi->ops->shutdown(nhi); } -static void nhi_check_quirks(struct tb_nhi *nhi) -{ - if (nhi->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 (nhi->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_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_check_iommu(struct tb_nhi *nhi) -{ - struct pci_bus *bus = nhi->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_check_iommu_pdev, &port_ok); - - nhi->iommu_dma_protection = port_ok; - dev_dbg(&nhi->pdev->dev, "IOMMU DMA protection is %s\n", - str_enabled_disabled(port_ok)); -} - static void nhi_reset(struct tb_nhi *nhi) { ktime_t timeout; @@ -1242,7 +1140,7 @@ static void nhi_reset(struct tb_nhi *nhi) return; if (!host_reset) { - dev_dbg(&nhi->pdev->dev, "skipping host router reset\n"); + dev_dbg(nhi->dev, "skipping host router reset\n"); return; } @@ -1253,64 +1151,13 @@ static void nhi_reset(struct tb_nhi *nhi) do { val = ioread32(nhi->iobase + REG_RESET); if (!(val & REG_RESET_HRR)) { - dev_warn(&nhi->pdev->dev, "host router reset successful\n"); + dev_warn(nhi->dev, "host router reset successful\n"); return; } usleep_range(10, 20); } while (ktime_before(ktime_get(), timeout)); - dev_warn(&nhi->pdev->dev, "timeout resetting host router\n"); -} - -static int nhi_init_msi(struct tb_nhi *nhi) -{ - struct pci_dev *pdev = nhi->pdev; - struct device *dev = &pdev->dev; - int res, irq, nvec; - - /* In case someone left them on. */ - nhi_disable_interrupts(nhi); - - nhi_enable_int_throttling(nhi); - - ida_init(&nhi->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(nhi->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_imr_valid(struct pci_dev *pdev) -{ - u8 val; - - if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) - return !!val; - - return true; + dev_warn(nhi->dev, "timeout resetting host router\n"); } static struct tb *nhi_select_cm(struct tb_nhi *nhi) @@ -1336,62 +1183,47 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi) return tb; } -static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) +int nhi_probe(struct tb_nhi *nhi) { - struct device *dev = &pdev->dev; - struct tb_nhi *nhi; + struct device *dev = nhi->dev; struct tb *tb; int res; - if (!nhi_imr_valid(pdev)) - return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); + if (!nhi->ops) + return dev_err_probe(dev, -EINVAL, "NHI ops not set\n"); - res = pcim_enable_device(pdev); - if (res) - return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); - - nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL); - if (!nhi) - return -ENOMEM; - - nhi->pdev = pdev; - nhi->ops = (const struct tb_nhi_ops *)id->driver_data; - - 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"); + if (!nhi->ops->init_interrupts) + return dev_err_probe(dev, -EINVAL, "missing required NHI ops\n"); nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); - nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->tx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->tx_rings), GFP_KERNEL); - nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->rx_rings), GFP_KERNEL); if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; - nhi_check_quirks(nhi); - nhi_check_iommu(nhi); nhi_reset(nhi); - res = nhi_init_msi(nhi); + /* In case someone left them on. */ + nhi_disable_interrupts(nhi); + + res = nhi->ops->init_interrupts(nhi); if (res) - return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); + return dev_err_probe(dev, res, "cannot enable interrupts, aborting\n"); spin_lock_init(&nhi->lock); - res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + res = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (res) return dev_err_probe(dev, res, "failed to set DMA mask\n"); - pci_set_master(pdev); - - if (nhi->ops && nhi->ops->init) { + if (nhi->ops->init) { res = nhi->ops->init(nhi); if (res) - return res; + return dev_err_probe(dev, res, "NHI specific init failed\n"); } tb = nhi_select_cm(nhi); @@ -1401,6 +1233,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + init_completion(&nhi->domain_released); + res = tb_domain_add(tb, host_reset); if (res) { /* @@ -1408,40 +1242,28 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) * activated. Do a proper shutdown. */ tb_domain_put(tb); + wait_for_completion(&nhi->domain_released); nhi_shutdown(nhi); - return res; + return dev_err_probe(dev, res, "failed to add domain\n"); } - pci_set_drvdata(pdev, tb); + dev_set_drvdata(dev, tb); - device_wakeup_enable(&pdev->dev); + device_wakeup_enable(dev); - pm_runtime_allow(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_put_autosuspend(&pdev->dev); + pm_runtime_allow(dev); + pm_runtime_set_autosuspend_delay(dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_put_autosuspend(dev); return 0; } -static void nhi_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); - nhi_shutdown(nhi); -} - /* * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable * the tunnels asap. A corresponding pci quirk blocks the downstream bridges * resume_noirq until we are done. */ -static const struct dev_pm_ops nhi_pm_ops = { +const struct dev_pm_ops nhi_pm_ops = { .suspend_noirq = nhi_suspend_noirq, .resume_noirq = nhi_resume_noirq, .freeze_noirq = nhi_freeze_noirq, /* @@ -1457,129 +1279,3 @@ static const struct dev_pm_ops nhi_pm_ops = { .runtime_suspend = nhi_runtime_suspend, .runtime_resume = nhi_runtime_resume, }; - -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_probe, - .remove = nhi_remove, - .shutdown = nhi_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); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 24ac4246d0ca..d488eadadfce 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -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 diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c deleted file mode 100644 index 96da07e88c52..000000000000 --- a/drivers/thunderbolt/nhi_ops.c +++ /dev/null @@ -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, -}; diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index cf5222bee971..d6a197fabc74 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -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 diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index 8713ea0f47c1..b2c322e76b8a 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -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; } diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c new file mode 100644 index 000000000000..bbd186c29ef7 --- /dev/null +++ b/drivers/thunderbolt/pci.c @@ -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); diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c index da2c59a17db5..fb19df8fca4e 100644 --- a/drivers/thunderbolt/property.c +++ b/drivers/thunderbolt/property.c @@ -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,57 +535,132 @@ 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; + list_add_tail(&p->list, &d->properties); + } - p->length = property->length; + return d; - 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; +err_free: + tb_property_free_dir(d); + return NULL; +} - 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; +static struct tb_property *tb_property_copy(const struct tb_property *property) +{ + struct tb_property *p; - 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; + p = tb_property_alloc(property->key, property->type); + if (!p) + return NULL; - case TB_PROPERTY_TYPE_VALUE: - p->value.immediate = property->value.immediate; - break; + 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; - default: - 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; - list_add_tail(&p->list, &d->properties); + 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 d; + return p; err_free: kfree(p); - tb_property_free_dir(d); - return NULL; } /** + * tb_property_copy_dir() - Take a deep copy of directory + * @dir: Directory to copy + * + * The resulting directory needs to be released by calling tb_property_free_dir(). + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. + */ +struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) +{ + return copy_dir(dir); +} +EXPORT_SYMBOL_GPL(tb_property_copy_dir); + +/** + * tb_property_merge_dir() - Merges directory into parent + * @parent: Directory to merge @dir + * @dir: Directory that is merged + * @replace: Replace existing entries + * + * This will merge @dir into @parent. Both must have same UUID. The + * properties in @dir will overwrite overlapping properties in @parent + * if @replace is %true. Contents of @dir is copied (so if it is not + * needed afterwards it needs to relesed by calling tb_property_free_dir()). + */ +int tb_property_merge_dir(struct tb_property_dir *parent, + const struct tb_property_dir *dir, + bool replace) +{ + const struct tb_property *property; + + if (WARN_ON(parent == dir)) + return -EINVAL; + + if (!uuid_equal(parent->uuid, dir->uuid)) + return -EINVAL; + + list_for_each_entry(property, &dir->properties, list) { + struct tb_property *p, *tmp; + + tmp = tb_property_copy(property); + if (!tmp) + return -ENOMEM; + + p = tb_property_find(parent, property->key, property->type); + if (p) { + if (replace) { + /* + * Found existing property in parent so + * replace with the new one. + */ + list_replace(&p->list, &tmp->list); + tb_property_free(p); + } else { + tb_property_free(tmp); + continue; + } + } else { + list_add_tail(&tmp->list, &parent->properties); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(tb_property_merge_dir); + +/** * tb_property_add_immediate() - Add immediate property to directory * @parent: Directory to add the property * @key: Key for the property diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c new file mode 100644 index 000000000000..c1f5c55583d0 --- /dev/null +++ b/drivers/thunderbolt/stream.c @@ -0,0 +1,1698 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Stream data over Thunderbolt/USB4 cable + * + * Copyright (C) 2026, Intel Corporation + * Authors: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + */ + +#define pr_fmt(fmt) "tbstream: " fmt + +#include <linux/configfs.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/sizes.h> +#include <linux/thunderbolt.h> +#include <linux/uaccess.h> +#include <linux/uio.h> +#include <linux/uuid.h> +#include <linux/wait.h> + +/* + * USB4STREAM - Stream data directly over Thunderbolt/USB4 cable + * + * HopIDs are configured by the user. In Linux this is done through + * ConfigFS. Once that is done paths are be established the first time + * the stream is opened. Typically the read side is opened first to make + * sure all the data will be received. + * + * End-to-end flow control is mandatory on both sides. + * + * Data is sent to the other side as tunneled DATA packets. All the data + * is owned by the user and passed as-is from the writer to the reader. + * + * Once the stream device is closed, a CLOSE packet is sent to the peer + * so it can take the necessary action. On Linux this typically results + * in EOF being returned to the reader. + * + * Tunneled packet types: + * + * +-------+---------+------------------+ + * | PDF | Type | Payload size | + * +-------+---------+------------------+ + * | 2 | DATA | up to 4 KiB | + * | 3 | CLOSE | up to 256 bytes | + * +-------+---------+------------------+ + * + * Each stream can optionally publish configuration values under its own + * XDomain property directory. The name of the directory is the name of + * the stream in question and the UUID is up to the stream. For example + * if the stream exposes video output then the directory name could be + * "video". + * + * Below values are reserved and can be used by the stream: + * + * +----------+-----------+-------------------------+ + * | Key | Type | Contents | + * +----------+-----------+-------------------------+ + * | inhopid | IMMEDIATE | Configured input HopID | + * | outhopid | IMMEDIATE | Configured output HopID | + * +----------+-----------+-------------------------+ + * + * It is allowed to add more stream specific properties as well if the + * above are not enough. + */ + +#define TBSTREAM_DEV_RING_SIZE 256 +#define TBSTREAM_DEV_MIN_RING_SIZE 32 +#define TBSTREAM_DEV_MAX_RING_SIZE 4096 +#define TBSTREAM_DEV_THROTTLING 8192 +#define TBSTREAM_DEV_MAX_THROTTLING 16776960 + +/** + * enum tbstream_frame_pdf - PDF numbers for tunneled frames + * @TBSTREAM_FRAME_START: PDF of the start of the frame + * @TBSTREAM_DATA: PDF of the DATA frame + * @TBSTREAM_CLOSE: PDF of the CLOSE frame + */ +enum tbstream_frame_pdf { + TBSTREAM_FRAME_START = 1, + TBSTREAM_DATA, + TBSTREAM_CLOSE, +}; + +/** + * struct tbstream_frame - Frame submitted to/from the rings + * @sdev: Pointer to the stream device + * @page: Page holding the packet + * @offset: Offset inside @page if partial read is done + * @completed: %true if the RX frame is completed + * @frame: Underlying frame structure + */ +struct tbstream_frame { + struct tbstream_dev *sdev; + struct page *page; + unsigned int offset; + bool completed; + struct ring_frame frame; +}; + +/** + * struct tbstream_ring - Stream RX/TX ring structure + * @ring: Pointer to the API ring + * @prod: Current value of producer + * @cons: Current value of consumer + * @frames: Holds the ring frames + */ +struct tbstream_ring { + struct tb_ring *ring; + unsigned long prod; + unsigned long cons; + struct tbstream_frame *frames; +}; + +/** + * struct tbstream_dev - Stream character device + * @group: ConfigFS group for this device + * @stream: Pointer to the stream if it is attached (%NULL otherwise) + * @misc: Character device used for tunneling + * @kref: Reference count + * @index: Unique identifier for the character device + * @in_hopid: In HopID + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. + * @wait: Waitqueue for open, read and write + * @lock: Lock protecting this structure + * @tx_ring: Transmit ring + * @rx_ring: Receive ring + * @list: Stream devices are linked through this + */ +struct tbstream_dev { + struct config_group group; + struct tbstream *stream; + struct miscdevice misc; + struct kref kref; + int index; + int in_hopid; + int out_hopid; + unsigned int ring_size; + unsigned int throttling; + int users; + bool closed; + bool removed; + wait_queue_head_t wait; + struct mutex lock; + struct tbstream_ring tx_ring; + struct tbstream_ring rx_ring; + struct list_head list; +}; + +/** + * struct tbstream_group - Config group for stream + * @group: ConfigFS group for @stream + * @stream: Stream the ConfigFS group is attached to. %NULL if there is + * no stream attached. + * @lock: Lock protecting this structure + * @dev_list: List of stream devices + * + * This is the ConfigFS directory for one connection to another host. + * There can be several &struct stream_dev linked through @dev_list of + * this structure. Reference count managed through @group. + */ +struct tbstream_group { + struct config_group group; + struct tbstream *stream; + struct mutex lock; + struct list_head dev_list; +}; + +/** + * struct tbstream - Stream service private data + * @kref: Reference count + * @svc: Pointer to the service device + * @list: Streams are linked through this in @stream_list + * + * This represents the actual physical connection between two hosts. + */ +struct tbstream { + struct kref kref; + struct tb_service *svc; + struct list_head list; +}; + +static DEFINE_IDA(tbstream_indices); + +/* Protects tbstream_list */ +static DEFINE_MUTEX(tbstream_lock); +static LIST_HEAD(tbstream_list); + +/* Serializes tbstream_get()/put() */ +static DEFINE_MUTEX(tbstream_kref_lock); + +/* Serializes tbstream_dev_get()/put() */ +static DEFINE_MUTEX(tbstream_dev_kref_lock); + +/* Stream property directory UUID: 3a1cb984-c4d9-4469-a277-ce2fdfd11f0d */ +static const uuid_t tbstream_dir_uuid = + UUID_INIT(0x3a1cb984, 0xc4d9, 0x4469, + 0xa2, 0x77, 0xce, 0x2f, 0xdf, 0xd1, 0x1f, 0x0d); + +static struct tb_property_dir *tbstream_dir; + +static void tbstream_release(struct kref *kref) +{ + struct tbstream *stream = container_of(kref, typeof(*stream), kref); + + tb_service_put(stream->svc); + kfree(stream); +} + +static void tbstream_put(struct tbstream *stream) +{ + if (stream) { + guard(mutex)(&tbstream_kref_lock); + kref_put(&stream->kref, tbstream_release); + } +} + +static struct tbstream *tbstream_get(struct tbstream *stream) +{ + if (stream) { + guard(mutex)(&tbstream_kref_lock); + kref_get(&stream->kref); + } + return stream; +} + +static inline bool tbstream_valid(const struct tbstream *stream) +{ + if (stream) + return !tb_service_parent(stream->svc)->is_unplugged; + return false; +} + +static void tbstream_ring_free(struct tbstream_ring *ring) +{ + struct device *dma_dev = tb_ring_dma_device(ring->ring); + enum dma_data_direction dir; + int i; + + if (ring->ring->is_tx) + dir = DMA_TO_DEVICE; + else + dir = DMA_FROM_DEVICE; + + for (i = 0; i < tb_ring_size(ring->ring); i++) { + struct tbstream_frame *sf = &ring->frames[i]; + + if (sf->frame.buffer_phy) + dma_unmap_page(dma_dev, sf->frame.buffer_phy, + tb_ring_frame_size(&sf->frame), dir); + sf->frame.buffer_phy = 0; + if (sf->page) + __free_page(sf->page); + sf->page = NULL; + } + + ring->prod = 0; + ring->cons = 0; + kfree(ring->frames); +} + +static inline bool tbstream_ring_available(const struct tbstream_ring *ring) +{ + return ring->prod > ring->cons; +} + +static inline struct tb_xdomain *tbstream_dev_xdomain(struct tbstream_dev *sdev) +{ + if (sdev->stream) + return tb_service_parent(sdev->stream->svc); + return NULL; +} + +static void tbstream_dev_release(struct kref *kref) +{ + struct tbstream_dev *sdev = container_of(kref, struct tbstream_dev, kref); + + if (sdev->stream) { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + + if (sdev->out_hopid > 0) + tb_xdomain_release_out_hopid(xd, sdev->out_hopid); + if (sdev->in_hopid > 0) + tb_xdomain_release_in_hopid(xd, sdev->in_hopid); + + tbstream_put(sdev->stream); + } + ida_free(&tbstream_indices, sdev->index); + kfree(sdev->misc.name); + kfree(sdev); +} + +static inline void tbstream_dev_put(struct tbstream_dev *sdev) +{ + guard(mutex)(&tbstream_dev_kref_lock); + kref_put(&sdev->kref, tbstream_dev_release); +} + +static inline struct tbstream_dev *tbstream_dev_get(struct tbstream_dev *sdev) +{ + guard(mutex)(&tbstream_dev_kref_lock); + kref_get(&sdev->kref); + return sdev; +} + +static inline struct tbstream_dev *to_tbstream_dev(struct miscdevice *misc) +{ + return container_of(misc, struct tbstream_dev, misc); +} + +static inline int tbstream_dev_valid(const struct tbstream_dev *sdev) +{ + const struct tbstream *stream = sdev->stream; + + if (!tbstream_valid(stream)) + return -ENXIO; + if (sdev->in_hopid <= 0 || sdev->out_hopid <= 0) + return -EINVAL; + return 0; +} + +static inline bool tbstream_dev_removed(const struct tbstream_dev *sdev) +{ + return sdev->removed; +} + +static inline bool tbstream_dev_closed(const struct tbstream_dev *sdev) +{ + return sdev->closed; +} + +static void +tbstream_dev_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame); + struct tbstream_dev *sdev = sf->sdev; + + if (canceled) + return; + + sf->completed = true; + sdev->rx_ring.prod++; + + if (sf->frame.flags & RING_DESC_CRC_ERROR) + pr_warn("RX CRC error\n"); + else if (sf->frame.flags & RING_DESC_BUFFER_OVERRUN) + pr_warn("RX buffer overrun\n"); + else + wake_up_interruptible_poll(&sdev->wait, EPOLLIN | EPOLLRDNORM); +} + +static struct tbstream_frame * +tbstream_dev_completed_rx(struct tbstream_dev *sdev) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring); + struct tbstream_frame *sf; + int index; + + index = sdev->rx_ring.cons % tb_ring_size(sdev->rx_ring.ring); + sf = &sdev->rx_ring.frames[index]; + if (!sf->completed) + return NULL; + + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, + tb_ring_frame_size(&sf->frame), + DMA_FROM_DEVICE); + return sf; +} + +static int tbstream_dev_consume_rx(struct tbstream_dev *sdev) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring); + struct tbstream_frame *sf; + int index; + + index = sdev->rx_ring.cons % tb_ring_size(sdev->rx_ring.ring); + sdev->rx_ring.cons++; + + sf = &sdev->rx_ring.frames[index]; + sf->completed = false; + sf->offset = 0; + sf->frame.size = 0; + + dma_sync_single_for_device(dma_dev, sf->frame.buffer_phy, + tb_ring_frame_size(&sf->frame), + DMA_FROM_DEVICE); + + return tb_ring_rx(sdev->rx_ring.ring, &sf->frame); +} + +static int tbstream_dev_alloc_rx_buffers(struct tbstream_dev *sdev) +{ + size_t ring_size = tb_ring_size(sdev->rx_ring.ring); + int i; + + sdev->rx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame), + GFP_KERNEL); + if (!sdev->rx_ring.frames) + return -ENOMEM; + + for (i = 0; i < ring_size; i++) { + struct device *dma_dev = tb_ring_dma_device(sdev->rx_ring.ring); + struct tbstream_frame *sf = &sdev->rx_ring.frames[i]; + dma_addr_t dma_addr; + + sf->page = alloc_page(GFP_KERNEL); + if (!sf->page) + return -ENOMEM; + + dma_addr = dma_map_page(dma_dev, sf->page, 0, TB_MAX_FRAME_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(dma_dev, dma_addr)) { + __free_page(sf->page); + sf->page = NULL; + return -ENOMEM; + } + + sf->sdev = sdev; + sf->frame.callback = tbstream_dev_rx_callback; + sf->frame.buffer_phy = dma_addr; + + tb_ring_rx(sdev->rx_ring.ring, &sf->frame); + } + + sdev->rx_ring.cons = 0; + sdev->rx_ring.prod = 0; + return 0; +} + +static void +tbstream_dev_tx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct tbstream_frame *sf = container_of(frame, typeof(*sf), frame); + struct tbstream_dev *sdev = sf->sdev; + + if (canceled) + return; + + sdev->tx_ring.prod++; + if (sf->frame.eof == TBSTREAM_DATA) + wake_up_interruptible_poll(&sdev->wait, EPOLLOUT | EPOLLWRNORM); +} + +static int tbstream_dev_alloc_tx_buffers(struct tbstream_dev *sdev) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->tx_ring.ring); + size_t ring_size = tb_ring_size(sdev->tx_ring.ring); + int i; + + sdev->tx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame), + GFP_KERNEL); + if (!sdev->tx_ring.frames) + return -ENOMEM; + + for (i = 0; i < ring_size; i++) { + struct tbstream_frame *sf = &sdev->tx_ring.frames[i]; + dma_addr_t dma_addr; + + sf->page = alloc_page(GFP_KERNEL); + if (!sf->page) + return -ENOMEM; + + dma_addr = dma_map_page(dma_dev, sf->page, 0, TB_MAX_FRAME_SIZE, + DMA_TO_DEVICE); + if (dma_mapping_error(dma_dev, dma_addr)) { + __free_page(sf->page); + sf->page = NULL; + return -ENOMEM; + } + + sf->sdev = sdev; + sf->frame.callback = tbstream_dev_tx_callback; + sf->frame.buffer_phy = dma_addr; + sf->frame.sof = TBSTREAM_FRAME_START; + } + + sdev->tx_ring.cons = 0; + sdev->tx_ring.prod = ring_size - 1; + return 0; +} + +static struct tbstream_frame * +tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, + struct iov_iter *from, size_t size) +{ + struct device *dma_dev = tb_ring_dma_device(sdev->tx_ring.ring); + struct tbstream_frame *sf; + int index; + + if (!tbstream_ring_available(&sdev->tx_ring)) + return ERR_PTR(-ENOBUFS); + + index = sdev->tx_ring.cons % tb_ring_size(sdev->tx_ring.ring); + sdev->tx_ring.cons++; + + sf = &sdev->tx_ring.frames[index]; + sf->frame.size = size < TB_MAX_FRAME_SIZE ? size : 0; + sf->frame.eof = pdf; + + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + if (pdf == TBSTREAM_DATA) { + if (copy_page_from_iter(sf->page, 0, size, from) != size) + return ERR_PTR(-EFAULT); + } else { + memset(page_address(sf->page), 0, size); + } + dma_sync_single_for_device(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + return sf; +} + +static int +tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + size_t size) +{ + struct tbstream_frame *sf; + + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_DATA, from, size); + if (IS_ERR(sf)) + return PTR_ERR(sf); + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); +} + +static int tbstream_dev_send_close(struct tbstream_dev *sdev) +{ + struct tbstream_frame *sf; + + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); +} + +static int tbstream_dev_start(struct tbstream_dev *sdev) +{ + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + + ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, + RING_FLAG_FRAME | RING_FLAG_E2E); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; + + ret = tbstream_dev_alloc_tx_buffers(sdev); + if (ret) + goto err_free_tx; + + e2e_tx_hop = ring->hop; + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + + ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, + RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, + sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; + } + sdev->rx_ring.ring = ring; + + ret = tb_xdomain_enable_paths(xd, sdev->out_hopid, + sdev->tx_ring.ring->hop, + sdev->in_hopid, + sdev->rx_ring.ring->hop); + if (ret) + goto err_free_rx; + + tb_ring_throttling(sdev->tx_ring.ring, sdev->throttling); + tb_ring_throttling(sdev->rx_ring.ring, sdev->throttling); + + tb_ring_start(sdev->tx_ring.ring); + tb_ring_start(sdev->rx_ring.ring); + + ret = tbstream_dev_alloc_rx_buffers(sdev); + if (ret) + goto err_stop; + return 0; + +err_stop: + tb_ring_stop(sdev->rx_ring.ring); + tb_ring_stop(sdev->tx_ring.ring); +err_free_rx: + tb_ring_free(sdev->rx_ring.ring); +err_free_tx_buffers: + tbstream_ring_free(&sdev->tx_ring); +err_free_tx: + tb_ring_free(sdev->tx_ring.ring); + + return ret; +} + +static void tbstream_dev_stop(struct tbstream_dev *sdev) +{ + struct tb_xdomain *xd; + + /* Wait for the ring to complete any outstanding frames */ + tb_ring_flush(sdev->tx_ring.ring, 500); + tb_ring_stop(sdev->tx_ring.ring); + tb_ring_flush(sdev->rx_ring.ring, 500); + tb_ring_stop(sdev->rx_ring.ring); + + xd = tbstream_dev_xdomain(sdev); + if (xd) { + tb_xdomain_disable_paths(xd, sdev->out_hopid, + sdev->tx_ring.ring->hop, + sdev->in_hopid, + sdev->rx_ring.ring->hop); + } + + tbstream_ring_free(&sdev->rx_ring); + tb_ring_free(sdev->rx_ring.ring); + sdev->rx_ring.ring = NULL; + tbstream_ring_free(&sdev->tx_ring); + tb_ring_free(sdev->tx_ring.ring); + sdev->tx_ring.ring = NULL; +} + +static ssize_t +tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) +{ + struct file *file = kiocb->ki_filp; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; + + ret = tbstream_dev_valid(sdev); + if (ret) + return ret; + + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + + while (!tbstream_ring_available(&sdev->rx_ring)) { + mutex_unlock(&sdev->lock); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible(sdev->wait, + tbstream_ring_available(&sdev->rx_ring) || + tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || + tbstream_dev_removed(sdev)); + if (ret) + return ret; + + ret = tbstream_dev_valid(sdev); + if (ret) + return ret; + + if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) + return 0; + + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + } + + nbytes = 0; + while (nbytes < iov_iter_count(to)) { + struct tbstream_frame *sf; + size_t size, sf_size; + + sf = tbstream_dev_completed_rx(sdev); + if (!sf) + break; + /* + * CLOSE tunneled packet. If userspace already read + * something then we stop processing now and return + * those bytes. Next time the first frame will be CLOSE + * in which case we return EOF to the user. + */ + if (sf->frame.eof == TBSTREAM_CLOSE) { + if (!nbytes) { + tbstream_dev_consume_rx(sdev); + sdev->closed = true; + } + break; + } + + sf_size = tb_ring_frame_size(&sf->frame); + size = min(iov_iter_count(to) - nbytes, sf_size); + + if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { + ret = -EFAULT; + break; + } + + /* + * If not all data from the frame is read so leave it in + * place and update the offset accordingly so next read + * gets the rest. + */ + if (size < sf_size) { + sf->offset += size; + sf->frame.size = sf_size - size; + } else { + ret = tbstream_dev_consume_rx(sdev); + if (ret) + break; + } + + nbytes += size; + } + + mutex_unlock(&sdev->lock); + if (ret) + return ret; + return nbytes; +} + +static ssize_t +tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) +{ + struct file *file = kiocb->ki_filp; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; + + ret = tbstream_dev_valid(sdev); + if (ret) + return ret; + + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + + while (!tbstream_ring_available(&sdev->tx_ring)) { + mutex_unlock(&sdev->lock); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible(sdev->wait, + tbstream_ring_available(&sdev->tx_ring) || + tbstream_dev_valid(sdev) != 0 || + tbstream_dev_closed(sdev) || + tbstream_dev_removed(sdev)); + if (ret) + return ret; + + ret = tbstream_dev_valid(sdev); + if (ret) + return ret; + + if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) + return -ENXIO; + + if (mutex_lock_interruptible(&sdev->lock)) + return -ERESTARTSYS; + } + + nbytes = 0; + while (nbytes < iov_iter_count(from)) { + size_t size; + + size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); + ret = tbstream_dev_send_data(sdev, from, size); + if (ret) { + /* + * If there are no more buffers we are done for + * this write. + */ + if (ret == -ENOBUFS) + ret = 0; + break; + } + + nbytes += size; + } + + mutex_unlock(&sdev->lock); + if (ret) + return ret; + return nbytes; +} + +static __poll_t +tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) +{ + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { + mask |= EPOLLHUP | EPOLLERR; + } else { + if (tbstream_ring_available(&sdev->tx_ring)) + mask |= EPOLLOUT | EPOLLWRNORM; + if (tbstream_ring_available(&sdev->rx_ring)) + mask |= EPOLLIN | EPOLLRDNORM; + } + return mask; +} + +static int tbstream_dev_fops_open(struct inode *inode, struct file *file) +{ + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + int ret; + + tbstream_dev_get(sdev); + + if (mutex_lock_interruptible(&sdev->lock)) { + tbstream_dev_put(sdev); + return -ERESTARTSYS; + } + + /* + * If there is no stream attached yet, block until it appears + * unless this is opened in non-blocking mode. + */ + while ((ret = tbstream_dev_valid(sdev))) { + mutex_unlock(&sdev->lock); + + if (ret != -ENXIO || (file->f_flags & O_NONBLOCK)) + goto err_put; + + ret = wait_event_interruptible(sdev->wait, + tbstream_dev_valid(sdev) == 0 || + tbstream_dev_removed(sdev)); + if (ret) + goto err_put; + + if (tbstream_dev_removed(sdev)) { + ret = -ENXIO; + goto err_put; + } + + if (mutex_lock_interruptible(&sdev->lock)) { + ret = -ERESTARTSYS; + goto err_put; + } + } + + /* Only on first open we allocate rings and enable paths */ + if (!sdev->users++) { + ret = tbstream_dev_start(sdev); + if (ret) { + sdev->users--; + goto err_unlock; + } + sdev->closed = false; + } + + mutex_unlock(&sdev->lock); + return 0; + +err_unlock: + mutex_unlock(&sdev->lock); +err_put: + tbstream_dev_put(sdev); + + return ret; +} + +static int tbstream_dev_fops_release(struct inode *inode, struct file *file) +{ + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + + mutex_lock(&sdev->lock); + if (--sdev->users == 0) { + /* + * Send CLOSE tunneled packet to notify the other end + * that we are closing the file. We do this twice if the + * first one fails. + */ + tbstream_dev_send_close(sdev); + tbstream_dev_stop(sdev); + } + mutex_unlock(&sdev->lock); + + tbstream_dev_put(sdev); + return 0; +} + +static const struct file_operations tbstream_dev_fops = { + .owner = THIS_MODULE, + .llseek = noop_llseek, + .read_iter = tbstream_dev_fops_read_iter, + .write_iter = tbstream_dev_fops_write_iter, + .poll = tbstream_dev_fops_poll, + .open = tbstream_dev_fops_open, + .release = tbstream_dev_fops_release, +}; + +static inline struct tbstream_dev * +tbstream_dev_from_group(struct config_group *group) +{ + return container_of(group, struct tbstream_dev, group); +} + +static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%d\n", sdev->index); +} +CONFIGFS_ATTR_RO(tbstream_dev_, index); + +static ssize_t tbstream_dev_in_hopid_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%d\n", sdev->in_hopid); +} + +/* svc->lock must be held */ +static void service_remove_properties(struct tb_service *svc, const char *name) +{ + struct tb_property *p; + + if (!svc->local_properties) + return; + + p = tb_property_find(svc->local_properties, name, + TB_PROPERTY_TYPE_DIRECTORY); + if (p) { + tb_property_free_dir(p->value.dir); + tb_property_remove(p); + + dev_dbg(&svc->dev, "removed local directory %s\n", name); + + /* + * Is the service directory empty already? If it is then + * we can release it as well. + */ + tb_property_for_each(svc->local_properties, p) { + if (p->type == TB_PROPERTY_TYPE_DIRECTORY) + return; + } + + tb_property_free_dir(svc->local_properties); + svc->local_properties = NULL; + } +} + +static int service_update_properties(struct tb_service *svc, const char *name, + int in_hopid, int out_hopid) +{ + struct tb_property_dir *dir; + struct tb_property *p; + + guard(mutex)(&svc->lock); + + if (in_hopid < 8 || out_hopid < 8) { + service_remove_properties(svc, name); + return 0; + } + + if (!svc->local_properties) { + /* + * Add the service directory first time we + * populate the entries. + */ + svc->local_properties = tb_property_copy_dir(tbstream_dir); + if (!svc->local_properties) + return -ENOMEM; + } + + p = tb_property_find(svc->local_properties, name, + TB_PROPERTY_TYPE_DIRECTORY); + if (p) { + dir = p->value.dir; + + p = tb_property_find(dir, "inhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate != in_hopid) + p->value.immediate = in_hopid; + p = tb_property_find(dir, "outhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate != out_hopid) + p->value.immediate = out_hopid; + + dev_dbg(&svc->dev, + "updated local directory %s: in HopID %d, out HopID %d\n", + name, in_hopid, out_hopid); + } else { + uuid_t uuid; + int ret; + + uuid_gen(&uuid); + dir = tb_property_create_dir(&uuid); + if (!dir) + return -ENOMEM; + + tb_property_add_immediate(dir, "inhopid", in_hopid); + tb_property_add_immediate(dir, "outhopid", out_hopid); + + ret = tb_property_add_dir(svc->local_properties, name, dir); + if (ret) { + tb_property_free_dir(dir); + return ret; + } + + dev_dbg(&svc->dev, + "added local directory %s: in HopID %d, out HopID %d\n", + name, in_hopid, out_hopid); + } + + return 0; +} + +static int tbstream_dev_update_properties(struct tbstream_dev *sdev) +{ + struct tbstream *stream; + int ret; + + stream = tbstream_get(sdev->stream); + if (!stream) + return 0; + + ret = service_update_properties(stream->svc, + config_item_name(&sdev->group.cg_item), + sdev->in_hopid, sdev->out_hopid); + if (!ret) + tb_service_properties_changed(stream->svc); + + tbstream_put(stream); + return ret; +} + +static int tbstream_dev_alloc_in_hopid(struct tbstream_dev *sdev, int hopid) +{ + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + int ret; + + if (sdev->in_hopid > 0 && sdev->in_hopid != hopid) + tb_xdomain_release_in_hopid(xd, sdev->in_hopid); + if (!hopid) { + sdev->in_hopid = hopid; + return 0; + } + ret = tb_xdomain_alloc_in_hopid(xd, hopid); + if (ret < 0) + return ret; + /* + * If specific HopID was asked by the user and we did not get + * that one then release and return error instead. + */ + if (hopid > 0 && hopid != ret) { + tb_xdomain_release_in_hopid(xd, ret); + return -EBUSY; + } + sdev->in_hopid = ret; + return 0; +} + +static int tbstream_dev_alloc_out_hopid(struct tbstream_dev *sdev, int hopid) +{ + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); + int ret; + + if (sdev->out_hopid > 0 && sdev->out_hopid != hopid) + tb_xdomain_release_out_hopid(xd, sdev->out_hopid); + if (!hopid) { + sdev->out_hopid = hopid; + return 0; + } + ret = tb_xdomain_alloc_out_hopid(xd, hopid); + if (ret < 0) + return ret; + if (hopid > 0 && hopid != ret) { + tb_xdomain_release_out_hopid(xd, ret); + return -EBUSY; + } + sdev->out_hopid = ret; + return 0; +} + +static ssize_t +tbstream_dev_in_hopid_store(struct config_item *item, const char *buf, + size_t count) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + int ret, in_hopid; + + ret = kstrtoint(buf, 0, &in_hopid); + if (ret) + return ret; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + if (sdev->stream) { + ret = tbstream_dev_alloc_in_hopid(sdev, in_hopid); + if (ret) + return ret; + ret = tbstream_dev_update_properties(sdev); + } else { + sdev->in_hopid = in_hopid; + } + return ret ? ret : count; +} +CONFIGFS_ATTR(tbstream_dev_, in_hopid); + +static ssize_t tbstream_dev_out_hopid_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%d\n", sdev->out_hopid); +} + +static ssize_t +tbstream_dev_out_hopid_store(struct config_item *item, const char *buf, + size_t count) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + int ret, out_hopid; + + ret = kstrtoint(buf, 0, &out_hopid); + if (ret) + return ret; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + if (sdev->stream) { + ret = tbstream_dev_alloc_out_hopid(sdev, out_hopid); + if (ret) + return ret; + ret = tbstream_dev_update_properties(sdev); + } else { + sdev->out_hopid = out_hopid; + } + return ret ? ret : count; +} +CONFIGFS_ATTR(tbstream_dev_, out_hopid); + +static ssize_t tbstream_dev_ring_size_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%u\n", sdev->ring_size); +} + +static ssize_t +tbstream_dev_ring_size_store(struct config_item *item, const char *buf, + size_t count) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + unsigned int ring_size; + int ret; + + ret = kstrtouint(buf, 0, &ring_size); + if (ret) + return ret; + + if (ring_size < TBSTREAM_DEV_MIN_RING_SIZE || + ring_size > TBSTREAM_DEV_MAX_RING_SIZE) + return -EINVAL; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + sdev->ring_size = ring_size; + return count; +} +CONFIGFS_ATTR(tbstream_dev_, ring_size); + +static ssize_t tbstream_dev_throttling_show(struct config_item *item, char *buf) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + return sysfs_emit(buf, "%u\n", sdev->throttling); +} + +static ssize_t +tbstream_dev_throttling_store(struct config_item *item, const char *buf, + size_t count) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + unsigned int throttling; + int ret; + + ret = kstrtouint(buf, 0, &throttling); + if (ret) + return ret; + + if (throttling > TBSTREAM_DEV_MAX_THROTTLING) + return -EINVAL; + + guard(mutex)(&sdev->lock); + if (sdev->users) + return -EBUSY; + sdev->throttling = throttling; + return count; +} +CONFIGFS_ATTR(tbstream_dev_, throttling); + +static struct configfs_attribute *tbstream_dev_attrs[] = { + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, + &tbstream_dev_attr_ring_size, + &tbstream_dev_attr_throttling, + NULL, +}; + +static void tbstream_dev_item_release(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(group); + + misc_deregister(&sdev->misc); + tbstream_dev_put(sdev); +} + +static struct configfs_item_operations tbstream_dev_item_ops = { + .release = tbstream_dev_item_release, +}; + +static const struct config_item_type tbstream_dev_type = { + .ct_owner = THIS_MODULE, + .ct_item_ops = &tbstream_dev_item_ops, + .ct_attrs = tbstream_dev_attrs, +}; + +static void service_get_hopids(struct tb_service *svc, const char *name, + int *in_hopid, int *out_hopid) +{ + struct tb_property_dir *dir; + struct tb_property *p; + + guard(mutex)(&svc->lock); + + /* See if we have directory entry with the matching name */ + p = tb_property_find(svc->remote_properties, name, + TB_PROPERTY_TYPE_DIRECTORY); + if (!p) + return; + + dir = p->value.dir; + + /* + * We need to reverse the HopIDs on our end so that in becomes + * out and vice versa. + */ + p = tb_property_find(dir, "inhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate >= 8) + *out_hopid = p->value.immediate; + p = tb_property_find(dir, "outhopid", TB_PROPERTY_TYPE_VALUE); + if (p && p->value.immediate >= 8) + *in_hopid = p->value.immediate; +} + +static void +tbstream_dev_attach_stream(struct tbstream_dev *sdev, struct tbstream_group *sg) +{ + const char *name = config_item_name(&sdev->group.cg_item); + struct tbstream *stream; + + stream = tbstream_get(sg->stream); + if (!stream) + return; + + scoped_guard(mutex, &sdev->lock) { + sdev->stream = stream; + /* + * If there is no existing configuration (or automatic + * configuration is being used) check if the other side + * has configuration for this and use it. + */ + if (sdev->in_hopid <= 0 && sdev->out_hopid <= 0) + service_get_hopids(stream->svc, name, &sdev->in_hopid, + &sdev->out_hopid); + if (sdev->in_hopid) + tbstream_dev_alloc_in_hopid(sdev, sdev->in_hopid); + if (sdev->out_hopid) + tbstream_dev_alloc_out_hopid(sdev, sdev->out_hopid); + } + + service_update_properties(stream->svc, name, sdev->in_hopid, + sdev->out_hopid); + tb_service_properties_changed(stream->svc); + + /* Notify any openerers that the stream is now attached */ + wake_up_interruptible(&sdev->wait); +} + +static void tbstream_dev_detach_stream(struct tbstream_dev *sdev) +{ + const char *name = config_item_name(&sdev->group.cg_item); + struct tbstream *stream; + struct tb_xdomain *xd; + + scoped_guard(mutex, &sdev->lock) { + stream = sdev->stream; + if (!stream) + return; + sdev->stream = NULL; + xd = tb_service_parent(stream->svc); + if (sdev->out_hopid > 0) + tb_xdomain_release_out_hopid(xd, sdev->out_hopid); + if (sdev->in_hopid > 0) + tb_xdomain_release_in_hopid(xd, sdev->in_hopid); + } + + service_update_properties(stream->svc, name, 0, 0); + tb_service_properties_changed(stream->svc); + + tbstream_put(stream); + + /* Notify any task that the stream is not valid anymore */ + wake_up_interruptible_poll(&sdev->wait, EPOLLHUP | EPOLLERR); +} + +static inline struct tbstream_group * +to_tbstream_group(struct config_group *group) +{ + return container_of(group, struct tbstream_group, group); +} + +static struct config_group * +tbstream_dev_make_group(struct config_group *group, const char *name) +{ + struct tbstream_group *sg = to_tbstream_group(group); + struct tbstream_dev *sdev; + int ret, index; + + /* + * We want the names to be suitable for passing as property + * directory names. + */ + if (strlen(name) > TB_PROPERTY_KEY_SIZE) + return ERR_PTR(-ENAMETOOLONG); + + sdev = kzalloc_obj(*sdev, GFP_KERNEL); + if (!sdev) + return ERR_PTR(-ENOMEM); + + index = ida_alloc(&tbstream_indices, GFP_KERNEL); + if (index < 0) { + kfree(sdev); + return ERR_PTR(index); + } + + sdev->index = index; + sdev->ring_size = TBSTREAM_DEV_RING_SIZE; + sdev->throttling = TBSTREAM_DEV_THROTTLING; + mutex_init(&sdev->lock); + init_waitqueue_head(&sdev->wait); + INIT_LIST_HEAD(&sdev->list); + /* This point forward tbstream_dev_put() must be used to release sdev */ + kref_init(&sdev->kref); + + config_group_init_type_name(&sdev->group, name, &tbstream_dev_type); + + scoped_guard(mutex, &sg->lock) + list_add_tail(&sdev->list, &sg->dev_list); + + tbstream_dev_attach_stream(sdev, sg); + + sdev->misc.name = kasprintf(GFP_KERNEL, "tbstream%d", index); + sdev->misc.minor = MISC_DYNAMIC_MINOR; + sdev->misc.fops = &tbstream_dev_fops; + + ret = misc_register(&sdev->misc); + if (ret) { + tbstream_dev_detach_stream(sdev); + scoped_guard(mutex, &sg->lock) + list_del(&sdev->list); + /* Calls tbstream_dev_put() */ + config_group_put(&sdev->group); + return ERR_PTR(ret); + } + + return &sdev->group; +} + +static void +tbstream_dev_drop_item(struct config_group *group, struct config_item *item) +{ + struct config_group *sdev_group = to_config_group(item); + struct tbstream_dev *sdev = tbstream_dev_from_group(sdev_group); + struct tbstream_group *sg = to_tbstream_group(group); + + scoped_guard(mutex, &sg->lock) + list_del(&sdev->list); + /* Notify any task that the underlying group was removed */ + sdev->removed = true; + wake_up_interruptible_poll(&sdev->wait, EPOLLHUP | EPOLLERR); + config_item_put(item); +} + +static struct configfs_group_operations tbstream_dev_group_ops = { + .make_group = tbstream_dev_make_group, + .drop_item = tbstream_dev_drop_item, +}; + +static void tbstream_item_release(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + struct tbstream_group *sg = to_tbstream_group(group); + + tbstream_put(sg->stream); + kfree(sg); +} + +static struct configfs_item_operations tbstream_item_ops = { + .release = tbstream_item_release, +}; + +static const struct config_item_type tbstream_dev_group_type = { + .ct_owner = THIS_MODULE, + .ct_group_ops = &tbstream_dev_group_ops, + .ct_item_ops = &tbstream_item_ops, +}; + +static struct config_group * +tbstream_make_group(struct config_group *group, const char *name) +{ + struct tbstream_group *sg; + struct tbstream *stream; + int domain, index; + u64 route; + + /* Make sure the format is correct */ + if (sscanf(name, "%u-%llx.%u", &domain, &route, &index) != 3) + return ERR_PTR(-EINVAL); + + sg = kzalloc_obj(*sg, GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + mutex_init(&sg->lock); + INIT_LIST_HEAD(&sg->dev_list); + + guard(mutex)(&tbstream_lock); + list_for_each_entry(stream, &tbstream_list, list) { + tbstream_get(stream); + if (sysfs_streq(name, dev_name(&stream->svc->dev))) { + sg->stream = stream; + break; + } + tbstream_put(stream); + } + + config_group_init_type_name(&sg->group, name, &tbstream_dev_group_type); + return &sg->group; +} + +static struct configfs_group_operations tbstream_group_ops = { + .make_group = tbstream_make_group, +}; + +static const struct config_item_type tbstream_group_type = { + .ct_owner = THIS_MODULE, + .ct_group_ops = &tbstream_group_ops, +}; + +static struct config_group tbstream_group = { + .cg_item = { + .ci_namebuf = "stream", + .ci_type = &tbstream_group_type, + }, +}; + +/* Returns reference count increased */ +static struct tbstream_group *tbstream_group_find(struct tbstream *stream) +{ + const char *name = dev_name(&stream->svc->dev); + struct config_item *item; + + guard(mutex)(&tbstream_group.cg_subsys->su_mutex); + item = config_group_find_item(&tbstream_group, name); + if (!item) + return NULL; + return to_tbstream_group(to_config_group(item)); +} + +static void tbstream_group_attach_stream(struct tbstream *stream) +{ + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return; + + guard(mutex)(&sg->lock); + if (WARN_ON(sg->stream)) { + config_group_put(&sg->group); + return; + } + sg->stream = tbstream_get(stream); + /* + * If there are existing stream devices, attach the stream to + * them now. + */ + list_for_each_entry(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + tbstream_dev_attach_stream(sdev, sg); + tbstream_dev_put(sdev); + } + + config_group_put(&sg->group); +} + +static void tbstream_group_detach_stream(struct tbstream *stream) +{ + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return; + + guard(mutex)(&sg->lock); + if (sg->stream) { + /* Detach this stream from the stream devices */ + list_for_each_entry_reverse(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + tbstream_dev_detach_stream(sdev); + tbstream_dev_put(sdev); + } + tbstream_put(sg->stream); + sg->stream = NULL; + } + + config_group_put(&sg->group); +} + +static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) +{ + struct tbstream *stream; + + stream = kzalloc_obj(*stream, GFP_KERNEL); + if (!stream) + return -ENOMEM; + + /* After this point, release stream by calling tbstream_put() */ + kref_init(&stream->kref); + stream->svc = tb_service_get(svc); + INIT_LIST_HEAD(&stream->list); + + scoped_guard(mutex, &tbstream_lock) + list_add_tail(&stream->list, &tbstream_list); + + tbstream_group_attach_stream(stream); + tb_service_set_drvdata(svc, stream); + return 0; +} + +static void tbstream_remove(struct tb_service *svc) +{ + struct tbstream *stream = tb_service_get_drvdata(svc); + + tbstream_group_detach_stream(stream); + scoped_guard(mutex, &tbstream_lock) + list_del(&stream->list); + tbstream_put(stream); +} + +static int __maybe_unused tbstream_suspend(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + struct tbstream *stream = tb_service_get_drvdata(svc); + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return 0; + + list_for_each_entry_reverse(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + /* Stop the stream (if it was open) */ + if (sdev->users) + tbstream_dev_stop(sdev); + tbstream_dev_put(sdev); + } + + config_group_put(&sg->group); + return 0; +} + +static int __maybe_unused tbstream_resume(struct device *dev) +{ + struct tb_service *svc = tb_to_service(dev); + struct tbstream *stream = tb_service_get_drvdata(svc); + struct tbstream_group *sg; + struct tbstream_dev *sdev; + + sg = tbstream_group_find(stream); + if (!sg) + return 0; + + list_for_each_entry(sdev, &sg->dev_list, list) { + tbstream_dev_get(sdev); + if (sdev->users) { + int ret; + + ret = tbstream_dev_start(sdev); + if (ret) { + tbstream_dev_put(sdev); + config_group_put(&sg->group); + return ret; + } + } + tbstream_dev_put(sdev); + } + + config_group_put(&sg->group); + return 0; +} + +static const struct dev_pm_ops tbstream_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tbstream_suspend, tbstream_resume) +}; + +static const struct tb_service_id tbstream_ids[] = { + { TB_SERVICE("stream", 1) }, + { }, +}; +MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); + +static struct tb_service_driver tbstream_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "thunderbolt_stream", + .pm = &tbstream_pm_ops, + }, + .probe = tbstream_probe, + .remove = tbstream_remove, + .id_table = tbstream_ids, +}; + +static int __init tbstream_init(void) +{ + int ret; + + tbstream_dir = tb_property_create_dir(&tbstream_dir_uuid); + if (!tbstream_dir) + return -ENOMEM; + + tb_property_add_immediate(tbstream_dir, "prtcid", 1); + tb_property_add_immediate(tbstream_dir, "prtcvers", 1); + tb_property_add_immediate(tbstream_dir, "prtcrevs", 0); + tb_property_add_immediate(tbstream_dir, "prtcstns", 0); + + ret = tb_register_property_dir("stream", tbstream_dir); + if (ret) + goto err_free_dir; + + config_group_init(&tbstream_group); + ret = tb_configfs_register_group(&tbstream_group); + if (ret) + goto err_unregister_dir; + + ret = tb_register_service_driver(&tbstream_driver); + if (ret) + goto err_unregister_group; + return 0; + +err_unregister_group: + tb_configfs_unregister_group(&tbstream_group); +err_unregister_dir: + tb_unregister_property_dir("stream", tbstream_dir); +err_free_dir: + tb_property_free_dir(tbstream_dir); + return ret; +} +module_init(tbstream_init); + +static void __exit tbstream_exit(void) +{ + tb_unregister_service_driver(&tbstream_driver); + tb_configfs_unregister_group(&tbstream_group); + tb_unregister_property_dir("stream", tbstream_dir); + tb_property_free_dir(tbstream_dir); + ida_destroy(&tbstream_indices); +} +module_exit(tbstream_exit); + +MODULE_AUTHOR("Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_DESCRIPTION("Stream data over Thunderbolt/USB4 cable"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c2ad58b19e7b..a830c82bb905 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -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; + } } } } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index c69c323e6952..76323255439a 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -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 @@ put_sw: 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)); } } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 217c3114bec8..ec9192b61bc0 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -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 diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index c0bf136236e6..92d893634d2b 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -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) diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 1f4318249c22..05652ee82fbf 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -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), { } }; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index f38f7753b6e4..b7f32305f14a 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -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; diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 9e810b2ae0b5..5fd4fe070f25 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -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); +} diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index c32d3516e780..890de530debc 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -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)) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 754808c43f00..fe6c5ac703f4 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -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,46 +2199,64 @@ 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); - stop_handshake(xd); - - device_for_each_child_reverse(&xd->dev, xd, unregister_service); + mutex_lock(&xd->lock); + xd->removing = true; + mutex_unlock(&xd->lock); + stop_handshake(xd); 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)) { + /* + * 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); - } else { - dev_info(&xd->dev, "host disconnected\n"); - device_unregister(&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); + + dev_info(&xd->dev, "host disconnected\n"); + device_unregister(&xd->dev); +} + +/** * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain * @xd: XDomain connection * @@ -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); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 0ba112175bb3..feb1af175cfd 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -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) |
