]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
thunderbolt: Notify userspace about software CM tunneling events
authorAlan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
Tue, 21 Jan 2025 17:46:21 +0000 (18:46 +0100)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Thu, 24 Apr 2025 05:24:39 +0000 (08:24 +0300)
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>
drivers/thunderbolt/tb.c
drivers/thunderbolt/tunnel.c
drivers/thunderbolt/tunnel.h

index 8c527af989271cccc6e544af8c18dc75e2aa5ebf..c14ab1fbeeafd689e28c0f2c04fa195f194cdf86 100644 (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;
index 76254ed3f47f4bbe4416214134a0dee5628ebf7e..d52efe3f658ce6943200efa8e22790b0557feda8 100644 (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;
 }
index 8a0a0cb21a895e0290afd57336f036d88903b030..5e9fb73d52200fbe40206cf50f83e52f714191e1 100644 (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...)                   \