mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
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:
commit
6381f99504
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...) \
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user