thunderbolt: Changes for v6.16 merge window

This includes following USB4/Thunderbolt changes for the v6.16 merge
 window:
 
   - Enable wake on connect and disconnect over system suspend.
   - Add mapping between Type-C ports and USB4 ports on non-Chrome systems.
   - Expose tunneling related events to userspace.
 
 All these have been in linux-next with no reported issues.
 -----BEGIN PGP SIGNATURE-----
 
 iQJUBAABCgA+FiEEVTdhRGBbNzLrSUBaAP2fSd+ZWKAFAmgsVNUgHG1pa2Eud2Vz
 dGVyYmVyZ0BsaW51eC5pbnRlbC5jb20ACgkQAP2fSd+ZWKCopQ/9GsI7l9d5gswZ
 w+LE1ouz5lOFlw+RV3EpMeb8nSzkTSoxtlM4gOlRg5zEec4l9MW6LUVQ/n0mBCNY
 R22Jc3KhgFqIX1G0XOW8t4g3piLAG3Q7NauzTSdRrC3bGKV73FjMw3WMSmlEE68E
 jQxmsfPnJdcM9joxCdHxIqVBfmTiv+IKU7+60a8YnIllfd+aaXcrbU4bRkgN/dbN
 f0Hw5av0K5K0qNejn/egaQHxBp9zJwzIitYTnLLncc5s0s44LPauJt+bxakeje92
 bae4oPJUZlJovOXwclT9alcZ78GjRNNx80CyF7QXVFWb6eXweKrOhveouyGaeXWw
 x4kJDZW2LroL5A1f+7i4iX6Ng9tqkCl18/KUGz+NDjghD9YTQtj1VYQlo0HEzX0O
 lnNxXPjkNAiTxxdGmcwhYSWZPGblTWfxYcrnrcnr11EBFWXyNw0i06sq4b1MdGpO
 2yTWlwQLFgLkMp003LUIUTiNVj7aEsAiPmHoApRfwcsehGLhPpPTpBDiFMLrvjwW
 ycZ5obGMsBsvrZMr3hSEACiGIT0j2pjl7IxVCaznjVW0qyaIv56mePBAxHCyL8nu
 wkDilYctsGwjIdBhsN4laZ7uGT3fByjBc6oetx+3VjY4ZO9oLKKQLcH49/ZGrfdP
 lZMUkiwyDR8Qsv8lNg6JXqg0SIFAqQE=
 =yU5Q
 -----END PGP SIGNATURE-----

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

Mika writes:

thunderbolt: Changes for v6.16 merge window

This includes following USB4/Thunderbolt changes for the v6.16 merge
window:

  - Enable wake on connect and disconnect over system suspend.
  - Add mapping between Type-C ports and USB4 ports on non-Chrome systems.
  - Expose tunneling related events to userspace.

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

* tag 'thunderbolt-for-v6.16-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt:
  Documentation/admin-guide: Document Thunderbolt/USB4 tunneling events
  thunderbolt: Notify userspace about firmware CM tunneling events
  thunderbolt: Notify userspace about software CM tunneling events
  thunderbolt: Introduce domain event message handler
  usb: typec: Connect Type-C port with associated USB4 port
  thunderbolt: Add Thunderbolt/USB4 <-> USB3 match function
  thunderbolt: Expose usb4_port_index() to other modules
  thunderbolt: Fix a logic error in wake on connect
  thunderbolt: Use wake on connect and disconnect over suspend
This commit is contained in:
Greg Kroah-Hartman 2025-05-21 12:26:51 +02:00
commit 6381f99504
13 changed files with 314 additions and 25 deletions

View File

@ -296,6 +296,39 @@ information is missing.
To recover from this mode, one needs to flash a valid NVM image to the
host controller in the same way it is done in the previous chapter.
Tunneling events
----------------
The driver sends ``KOBJ_CHANGE`` events to userspace when there is a
tunneling change in the ``thunderbolt_domain``. The notification carries
following environment variables::
TUNNEL_EVENT=<EVENT>
TUNNEL_DETAILS=0:12 <-> 1:20 (USB3)
Possible values for ``<EVENT>`` are:
activated
The tunnel was activated (created).
changed
There is a change in this tunnel. For example bandwidth allocation was
changed.
deactivated
The tunnel was torn down.
low bandwidth
The tunnel is not getting optimal bandwidth.
insufficient bandwidth
There is not enough bandwidth for the current tunnel requirements.
The ``TUNNEL_DETAILS`` is only provided if the tunnel is known. For
example, in case of Firmware Connection Manager this is missing or does
not provide full tunnel information. In case of Software Connection Manager
this includes full tunnel details. The format currently matches what the
driver uses when logging. This may change over time.
Networking over Thunderbolt cable
---------------------------------
Thunderbolt technology allows software communication between two hosts

View File

@ -217,7 +217,7 @@ static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr,
ret = tb->cm_ops->set_boot_acl(tb, acl, tb->nboot_acl);
if (!ret) {
/* Notify userspace about the change */
kobject_uevent(&tb->dev.kobj, KOBJ_CHANGE);
tb_domain_event(tb, NULL);
}
mutex_unlock(&tb->lock);

View File

@ -22,6 +22,7 @@
#include "ctl.h"
#include "nhi_regs.h"
#include "tb.h"
#include "tunnel.h"
#define PCIE2CIO_CMD 0x30
#define PCIE2CIO_CMD_TIMEOUT BIT(31)
@ -379,6 +380,27 @@ static bool icm_firmware_running(const struct tb_nhi *nhi)
return !!(val & REG_FW_STS_ICM_EN);
}
static void icm_xdomain_activated(struct tb_xdomain *xd, bool activated)
{
struct tb_port *nhi_port, *dst_port;
struct tb *tb = xd->tb;
nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI);
dst_port = tb_xdomain_downstream_port(xd);
if (activated)
tb_tunnel_event(tb, TB_TUNNEL_ACTIVATED, TB_TUNNEL_DMA,
nhi_port, dst_port);
else
tb_tunnel_event(tb, TB_TUNNEL_DEACTIVATED, TB_TUNNEL_DMA,
nhi_port, dst_port);
}
static void icm_dp_event(struct tb *tb)
{
tb_tunnel_event(tb, TB_TUNNEL_CHANGED, TB_TUNNEL_DP, NULL, NULL);
}
static bool icm_fr_is_supported(struct tb *tb)
{
return !x86_apple_machine;
@ -584,6 +606,7 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
if (reply.hdr.flags & ICM_FLAGS_ERROR)
return -EIO;
icm_xdomain_activated(xd, true);
return 0;
}
@ -603,6 +626,8 @@ static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
nhi_mailbox_cmd(tb->nhi, cmd, 1);
usleep_range(10, 50);
nhi_mailbox_cmd(tb->nhi, cmd, 2);
icm_xdomain_activated(xd, false);
return 0;
}
@ -1151,6 +1176,7 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
if (reply.hdr.flags & ICM_FLAGS_ERROR)
return -EIO;
icm_xdomain_activated(xd, true);
return 0;
}
@ -1191,7 +1217,12 @@ static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
return ret;
usleep_range(10, 50);
return icm_tr_xdomain_tear_down(tb, xd, 2);
ret = icm_tr_xdomain_tear_down(tb, xd, 2);
if (ret)
return ret;
icm_xdomain_activated(xd, false);
return 0;
}
static void
@ -1718,6 +1749,9 @@ static void icm_handle_notification(struct work_struct *work)
if (tb_is_xdomain_enabled())
icm->xdomain_disconnected(tb, n->pkg);
break;
case ICM_EVENT_DP_CONFIG_CHANGED:
icm_dp_event(tb);
break;
case ICM_EVENT_RTD3_VETO:
icm->rtd3_veto(tb, n->pkg);
break;

View File

@ -3599,6 +3599,7 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime)
flags |= TB_WAKE_ON_USB4;
flags |= TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE | TB_WAKE_ON_DP;
} else if (device_may_wakeup(&sw->dev)) {
flags |= TB_WAKE_ON_CONNECT | TB_WAKE_ON_DISCONNECT;
flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE;
}

View File

@ -952,6 +952,15 @@ static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw)
tb_port_dbg(up, "available bandwidth for new USB3 tunnel %d/%d Mb/s\n",
available_up, available_down);
/*
* If the available bandwidth is less than 1.5 Gb/s notify
* userspace that the connected isochronous device may not work
* properly.
*/
if (available_up < 1500 || available_down < 1500)
tb_tunnel_event(tb, TB_TUNNEL_LOW_BANDWIDTH, TB_TUNNEL_USB3,
down, up);
tunnel = tb_tunnel_alloc_usb3(tb, up, down, available_up,
available_down);
if (!tunnel) {
@ -2000,8 +2009,10 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in,
ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
true);
if (ret)
if (ret) {
tb_tunnel_event(tb, TB_TUNNEL_NO_BANDWIDTH, TB_TUNNEL_DP, in, out);
goto err_reclaim_usb;
}
tb_dbg(tb, "available bandwidth for new DP tunnel %u/%u Mb/s\n",
available_up, available_down);
@ -2622,8 +2633,12 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
}
}
return tb_tunnel_alloc_bandwidth(tunnel, requested_up,
requested_down);
ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up,
requested_down);
if (ret)
goto fail;
return 0;
}
/*
@ -2699,6 +2714,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
"failing the request by rewriting allocated %d/%d Mb/s\n",
allocated_up, allocated_down);
tb_tunnel_alloc_bandwidth(tunnel, &allocated_up, &allocated_down);
tb_tunnel_event(tb, TB_TUNNEL_NO_BANDWIDTH, TB_TUNNEL_DP, in, out);
}
return ret;

View File

@ -804,6 +804,19 @@ static inline void tb_domain_put(struct tb *tb)
put_device(&tb->dev);
}
/**
* tb_domain_event() - Notify userspace about an event in domain
* @tb: Domain where event occurred
* @envp: Array of uevent environment strings (can be %NULL)
*
* This function provides a way to notify userspace about any events
* that take place in the domain.
*/
static inline void tb_domain_event(struct tb *tb, char *envp[])
{
kobject_uevent_env(&tb->dev.kobj, KOBJ_CHANGE, envp);
}
struct tb_nvm *tb_nvm_alloc(struct device *dev);
int tb_nvm_read_version(struct tb_nvm *nvm);
int tb_nvm_validate(struct tb_nvm *nvm);
@ -1468,6 +1481,7 @@ static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev)
struct usb4_port *usb4_port_device_add(struct tb_port *port);
void usb4_port_device_remove(struct usb4_port *usb4);
int usb4_port_device_resume(struct usb4_port *usb4);
int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port);
static inline bool usb4_port_device_is_offline(const struct usb4_port *usb4)
{

View File

@ -118,6 +118,7 @@ enum icm_event_code {
ICM_EVENT_DEVICE_DISCONNECTED = 0x4,
ICM_EVENT_XDOMAIN_CONNECTED = 0x6,
ICM_EVENT_XDOMAIN_DISCONNECTED = 0x7,
ICM_EVENT_DP_CONFIG_CHANGED = 0x8,
ICM_EVENT_RTD3_VETO = 0xa,
};

View File

@ -100,6 +100,14 @@ MODULE_PARM_DESC(bw_alloc_mode,
static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" };
static const char * const tb_event_names[] = {
[TB_TUNNEL_ACTIVATED] = "activated",
[TB_TUNNEL_CHANGED] = "changed",
[TB_TUNNEL_DEACTIVATED] = "deactivated",
[TB_TUNNEL_LOW_BANDWIDTH] = "low bandwidth",
[TB_TUNNEL_NO_BANDWIDTH] = "insufficient bandwidth",
};
/* Synchronizes kref_get()/put() of struct tb_tunnel */
static DEFINE_MUTEX(tb_tunnel_lock);
@ -220,6 +228,72 @@ void tb_tunnel_put(struct tb_tunnel *tunnel)
mutex_unlock(&tb_tunnel_lock);
}
/**
* tb_tunnel_event() - Notify userspace about tunneling event
* @tb: Domain where the event occurred
* @event: Event that happened
* @type: Type of the tunnel in question
* @src_port: Tunnel source port (can be %NULL)
* @dst_port: Tunnel destination port (can be %NULL)
*
* Notifies userspace about tunneling @event in the domain. The tunnel
* does not need to exist (e.g the tunnel was not activated because
* there is not enough bandwidth). If the @src_port and @dst_port are
* given fill in full %TUNNEL_DETAILS environment variable. Otherwise
* uses the shorter one (just the tunnel type).
*/
void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event,
enum tb_tunnel_type type,
const struct tb_port *src_port,
const struct tb_port *dst_port)
{
char *envp[3] = { NULL };
if (WARN_ON_ONCE(event >= ARRAY_SIZE(tb_event_names)))
return;
if (WARN_ON_ONCE(type >= ARRAY_SIZE(tb_tunnel_names)))
return;
envp[0] = kasprintf(GFP_KERNEL, "TUNNEL_EVENT=%s", tb_event_names[event]);
if (!envp[0])
return;
if (src_port != NULL && dst_port != NULL) {
envp[1] = kasprintf(GFP_KERNEL, "TUNNEL_DETAILS=%llx:%u <-> %llx:%u (%s)",
tb_route(src_port->sw), src_port->port,
tb_route(dst_port->sw), dst_port->port,
tb_tunnel_names[type]);
} else {
envp[1] = kasprintf(GFP_KERNEL, "TUNNEL_DETAILS=(%s)",
tb_tunnel_names[type]);
}
if (envp[1])
tb_domain_event(tb, envp);
kfree(envp[1]);
kfree(envp[0]);
}
static inline void tb_tunnel_set_active(struct tb_tunnel *tunnel, bool active)
{
if (active) {
tunnel->state = TB_TUNNEL_ACTIVE;
tb_tunnel_event(tunnel->tb, TB_TUNNEL_ACTIVATED, tunnel->type,
tunnel->src_port, tunnel->dst_port);
} else {
tunnel->state = TB_TUNNEL_INACTIVE;
tb_tunnel_event(tunnel->tb, TB_TUNNEL_DEACTIVATED, tunnel->type,
tunnel->src_port, tunnel->dst_port);
}
}
static inline void tb_tunnel_changed(struct tb_tunnel *tunnel)
{
tb_tunnel_event(tunnel->tb, TB_TUNNEL_CHANGED, tunnel->type,
tunnel->src_port, tunnel->dst_port);
}
static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable)
{
struct tb_port *port = tb_upstream_port(tunnel->dst_port->sw);
@ -992,7 +1066,7 @@ static void tb_dp_dprx_work(struct work_struct *work)
return;
}
} else {
tunnel->state = TB_TUNNEL_ACTIVE;
tb_tunnel_set_active(tunnel, true);
}
mutex_unlock(&tb->lock);
}
@ -2326,7 +2400,7 @@ int tb_tunnel_activate(struct tb_tunnel *tunnel)
}
}
tunnel->state = TB_TUNNEL_ACTIVE;
tb_tunnel_set_active(tunnel, true);
return 0;
err:
@ -2356,7 +2430,7 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel)
if (tunnel->post_deactivate)
tunnel->post_deactivate(tunnel);
tunnel->state = TB_TUNNEL_INACTIVE;
tb_tunnel_set_active(tunnel, false);
}
/**
@ -2449,8 +2523,16 @@ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
if (!tb_tunnel_is_active(tunnel))
return -ENOTCONN;
if (tunnel->alloc_bandwidth)
return tunnel->alloc_bandwidth(tunnel, alloc_up, alloc_down);
if (tunnel->alloc_bandwidth) {
int ret;
ret = tunnel->alloc_bandwidth(tunnel, alloc_up, alloc_down);
if (ret)
return ret;
tb_tunnel_changed(tunnel);
return 0;
}
return -EOPNOTSUPP;
}

View File

@ -194,6 +194,29 @@ static inline bool tb_tunnel_direction_downstream(const struct tb_tunnel *tunnel
tunnel->dst_port);
}
/**
* enum tb_tunnel_event - Tunnel related events
* @TB_TUNNEL_ACTIVATED: A tunnel was activated
* @TB_TUNNEL_CHANGED: There is a tunneling change in the domain. Includes
* full %TUNNEL_DETAILS if the tunnel in question is known
* (ICM does not provide that information).
* @TB_TUNNEL_DEACTIVATED: A tunnel was torn down
* @TB_TUNNEL_LOW_BANDWIDTH: Tunnel bandwidth is not optimal
* @TB_TUNNEL_NO_BANDWIDTH: There is not enough bandwidth for a tunnel
*/
enum tb_tunnel_event {
TB_TUNNEL_ACTIVATED,
TB_TUNNEL_CHANGED,
TB_TUNNEL_DEACTIVATED,
TB_TUNNEL_LOW_BANDWIDTH,
TB_TUNNEL_NO_BANDWIDTH,
};
void tb_tunnel_event(struct tb *tb, enum tb_tunnel_event event,
enum tb_tunnel_type type,
const struct tb_port *src_port,
const struct tb_port *dst_port);
const char *tb_tunnel_type_name(const struct tb_tunnel *tunnel);
#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \

View File

@ -440,10 +440,10 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags)
bool configured = val & PORT_CS_19_PC;
usb4 = port->usb4;
if (((flags & TB_WAKE_ON_CONNECT) |
if (((flags & TB_WAKE_ON_CONNECT) &&
device_may_wakeup(&usb4->dev)) && !configured)
val |= PORT_CS_19_WOC;
if (((flags & TB_WAKE_ON_DISCONNECT) |
if (((flags & TB_WAKE_ON_DISCONNECT) &&
device_may_wakeup(&usb4->dev)) && configured)
val |= PORT_CS_19_WOD;
if ((flags & TB_WAKE_ON_USB4) && configured)
@ -935,7 +935,15 @@ int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
return status ? -EIO : 0;
}
static int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port)
/**
* usb4_port_index() - Finds matching USB4 port index
* @sw: USB4 router
* @port: USB4 protocol or lane adapter
*
* Finds matching USB4 port index (starting from %0) that given @port goes
* through.
*/
int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port)
{
struct tb_port *p;
int usb4_idx = 0;
@ -969,7 +977,7 @@ static int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port)
struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
const struct tb_port *port)
{
int usb4_idx = usb4_port_idx(sw, port);
int usb4_idx = usb4_port_index(sw, port);
struct tb_port *p;
int pcie_idx = 0;
@ -1000,7 +1008,7 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
const struct tb_port *port)
{
int usb4_idx = usb4_port_idx(sw, port);
int usb4_idx = usb4_port_index(sw, port);
struct tb_port *p;
int usb_idx = 0;

View File

@ -105,6 +105,49 @@ static void usb4_port_online(struct usb4_port *usb4)
tb_acpi_power_off_retimers(port);
}
/**
* usb4_usb3_port_match() - Matches USB4 port device with USB 3.x port device
* @usb4_port_dev: USB4 port device
* @usb3_port_fwnode: USB 3.x port firmware node
*
* Checks if USB 3.x port @usb3_port_fwnode is tunneled through USB4 port @usb4_port_dev.
* Returns true if match is found, false otherwise.
*
* Function is designed to be used with component framework (component_match_add).
*/
bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode)
{
struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = NULL;
struct usb4_port *usb4;
struct tb_switch *sw;
struct tb_nhi *nhi;
u8 usb4_port_num;
struct tb *tb;
usb4 = tb_to_usb4_port_device(usb4_port_dev);
if (!usb4)
return false;
sw = usb4->port->sw;
tb = sw->tb;
nhi = tb->nhi;
nhi_fwnode = fwnode_find_reference(usb3_port_fwnode, "usb4-host-interface", 0);
if (IS_ERR(nhi_fwnode))
return false;
/* Check if USB3 fwnode references same NHI where USB4 port resides */
if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode))
return false;
if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num))
return false;
return usb4_port_index(sw, usb4->port) == usb4_port_num;
}
EXPORT_SYMBOL_GPL(usb4_usb3_port_match);
static ssize_t offline_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@ -276,12 +319,10 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port)
return ERR_PTR(ret);
}
if (dev_fwnode(&usb4->dev)) {
ret = component_add(&usb4->dev, &connector_ops);
if (ret) {
dev_err(&usb4->dev, "failed to add component\n");
device_unregister(&usb4->dev);
}
ret = component_add(&usb4->dev, &connector_ops);
if (ret) {
dev_err(&usb4->dev, "failed to add component\n");
device_unregister(&usb4->dev);
}
if (!tb_is_upstream_port(port))
@ -306,8 +347,7 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port)
*/
void usb4_port_device_remove(struct usb4_port *usb4)
{
if (dev_fwnode(&usb4->dev))
component_del(&usb4->dev, &connector_ops);
component_del(&usb4->dev, &connector_ops);
device_unregister(&usb4->dev);
}

View File

@ -8,6 +8,7 @@
#include <linux/acpi.h>
#include <linux/component.h>
#include <linux/thunderbolt.h>
#include <linux/usb.h>
#include "class.h"
@ -36,6 +37,11 @@ struct each_port_arg {
struct component_match *match;
};
static int usb4_port_compare(struct device *dev, void *fwnode)
{
return usb4_usb3_port_match(dev, fwnode);
}
static int typec_port_compare(struct device *dev, void *fwnode)
{
return device_match_fwnode(dev, fwnode);
@ -51,9 +57,22 @@ static int typec_port_match(struct device *dev, void *data)
if (con_adev == adev)
return 0;
if (con_adev->pld_crc == adev->pld_crc)
if (con_adev->pld_crc == adev->pld_crc) {
struct fwnode_handle *adev_fwnode = acpi_fwnode_handle(adev);
component_match_add(&arg->port->dev, &arg->match, typec_port_compare,
acpi_fwnode_handle(adev));
adev_fwnode);
/*
* If dev is USB 3.x port, it may have reference to the
* USB4 host interface in which case we can also link the
* Type-C port with the USB4 port.
*/
if (fwnode_property_present(adev_fwnode, "usb4-host-interface"))
component_match_add(&arg->port->dev, &arg->match,
usb4_port_compare, adev_fwnode);
}
return 0;
}

View File

@ -11,6 +11,13 @@
#ifndef THUNDERBOLT_H_
#define THUNDERBOLT_H_
#include <linux/types.h>
struct fwnode_handle;
struct device;
#if IS_REACHABLE(CONFIG_USB4)
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/list.h>
@ -674,4 +681,15 @@ static inline struct device *tb_ring_dma_device(struct tb_ring *ring)
return &ring->nhi->pdev->dev;
}
bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode);
#else /* CONFIG_USB4 */
static inline bool usb4_usb3_port_match(struct device *usb4_port_dev,
const struct fwnode_handle *usb3_port_fwnode)
{
return false;
}
#endif /* CONFIG_USB4 */
#endif /* THUNDERBOLT_H_ */