2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00

thunderbolt: Notify userspace about software CM tunneling events

This adds notification whenever software connection manager activates,
changes or deactivates a tunnel, and also if there is limitation in
bandwidth.

The notification looks like below:

  TUNNEL_EVENT=activated|changed|deactivated|low bandwidth|
  	       insufficient bandwidth
  TUNNEL_DETAILS=0:12 <-> 1:20 (USB3)

Userspace can then listen these and inform user if needed. For example
if there is not enough bandwidth, it can show warning dialog to the user.

Signed-off-by: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
Alan Borzeszkowski 2025-01-21 18:46:21 +01:00 committed by Mika Westerberg
parent cdf9956b69
commit 785da9e6a1
3 changed files with 129 additions and 8 deletions

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 @@ fail:
"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

@ -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...) \