In addition to PCIe and Display Port tunnels it is also possible to
create tunnels that forward DMA traffic from the host interface adapter
(NHI) to a NULL port that is connected to another domain through a
Thunderbolt cable. These tunnels can be used to carry software messages
such as networking packets.

To support this we introduce another tunnel type (TB_TUNNEL_DMA) that
supports paths from NHI to NULL port and back.

Signed-off-by: Mika Westerberg <mika.westerb...@linux.intel.com>
---
 drivers/thunderbolt/path.c    | 20 ++++++--
 drivers/thunderbolt/switch.c  | 22 ++++++++
 drivers/thunderbolt/tb.h      |  2 +
 drivers/thunderbolt/tb_regs.h |  3 ++
 drivers/thunderbolt/tunnel.c  | 94 ++++++++++++++++++++++++++++++++++-
 drivers/thunderbolt/tunnel.h  | 10 ++++
 6 files changed, 147 insertions(+), 4 deletions(-)

diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
index ada60d4aa99b..afdb667fcc0d 100644
--- a/drivers/thunderbolt/path.c
+++ b/drivers/thunderbolt/path.c
@@ -284,7 +284,8 @@ static void __tb_path_deallocate_nfc(struct tb_path *path, 
int first_hop)
        }
 }
 
-static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index)
+static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
+                                   bool clear_fc)
 {
        struct tb_regs_hop hop;
        ktime_t timeout;
@@ -311,8 +312,20 @@ static int __tb_path_deactivate_hop(struct tb_port *port, 
int hop_index)
                if (ret)
                        return ret;
 
-               if (!hop.pending)
+               if (!hop.pending) {
+                       if (clear_fc) {
+                               /* Clear flow control */
+                               hop.ingress_fc = 0;
+                               hop.egress_fc = 0;
+                               hop.ingress_shared_buffer = 0;
+                               hop.egress_shared_buffer = 0;
+
+                               return tb_port_write(port, &hop, TB_CFG_HOPS,
+                                                    2 * hop_index, 2);
+                       }
+
                        return 0;
+               }
 
                usleep_range(10, 20);
        } while (ktime_before(ktime_get(), timeout));
@@ -326,7 +339,8 @@ static void __tb_path_deactivate_hops(struct tb_path *path, 
int first_hop)
 
        for (i = first_hop; i < path->path_length; i++) {
                res = __tb_path_deactivate_hop(path->hops[i].in_port,
-                                              path->hops[i].in_hop_index);
+                                              path->hops[i].in_hop_index,
+                                              path->clear_fc);
                if (res)
                        tb_port_warn(path->hops[i].in_port,
                                     "hop deactivation failed for hop %d, index 
%d\n",
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index a1876dcd1d10..13eed95fc667 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -562,6 +562,28 @@ int tb_port_add_nfc_credits(struct tb_port *port, int 
credits)
                             TB_CFG_PORT, 4, 1);
 }
 
+/**
+ * tb_port_set_initial_credits() - Set initial port link credits allocated
+ * @port: Port to set the initial credits
+ * @credits: Number of credits to to allocate
+ *
+ * Set initial credits value to be used for ingress shared buffering.
+ */
+int tb_port_set_initial_credits(struct tb_port *port, u32 credits)
+{
+       u32 data;
+       int ret;
+
+       ret = tb_port_read(port, &data, TB_CFG_PORT, 5, 1);
+       if (ret)
+               return ret;
+
+       data &= ~TB_PORT_LCA_MASK;
+       data |= (credits << TB_PORT_LCA_SHIFT) & TB_PORT_LCA_MASK;
+
+       return tb_port_write(port, &data, TB_CFG_PORT, 5, 1);
+}
+
 /**
  * tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER
  *
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 7e155eed1fee..3a42a47df69f 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -198,6 +198,7 @@ struct tb_path {
        int weight:4;
        bool drop_packages;
        bool activated;
+       bool clear_fc;
        struct tb_path_hop *hops;
        int path_length; /* number of hops */
 };
@@ -465,6 +466,7 @@ static inline struct tb_switch *tb_to_switch(struct device 
*dev)
 
 int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
 int tb_port_add_nfc_credits(struct tb_port *port, int credits);
+int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
 int tb_port_clear_counter(struct tb_port *port, int counter);
 int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
 void tb_port_release_in_hopid(struct tb_port *port, int hopid);
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
index 420d2a623f31..4591c8b1d546 100644
--- a/drivers/thunderbolt/tb_regs.h
+++ b/drivers/thunderbolt/tb_regs.h
@@ -215,6 +215,9 @@ struct tb_regs_port_header {
 #define TB_PORT_NFC_CREDITS_MASK       GENMASK(19, 0)
 #define TB_PORT_MAX_CREDITS_SHIFT      20
 #define TB_PORT_MAX_CREDITS_MASK       GENMASK(26, 20)
+/* DWORD 5 */
+#define TB_PORT_LCA_SHIFT              22
+#define TB_PORT_LCA_MASK               GENMASK(28, 22)
 
 /* Display Port adapter registers */
 
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 7aab7e07739b..f10a0a15b873 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -26,7 +26,10 @@
 #define TB_DP_AUX_PATH_OUT             1
 #define TB_DP_AUX_PATH_IN              2
 
-static const char * const tb_tunnel_names[] = { "PCI", "DP" };
+#define TB_DMA_PATH_OUT                        0
+#define TB_DMA_PATH_IN                 1
+
+static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA" };
 
 #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...)                   \
        do {                                                            \
@@ -461,6 +464,95 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct 
tb_port *in,
        return NULL;
 }
 
+static u32 tb_dma_credits(struct tb_port *nhi)
+{
+       u32 max_credits;
+
+       max_credits = nhi->config.nfc_credits & TB_PORT_MAX_CREDITS_MASK;
+       max_credits >>= TB_PORT_MAX_CREDITS_SHIFT;
+
+       return min(max_credits, 13U);
+}
+
+static int tb_dma_activate(struct tb_tunnel *tunnel, bool active)
+{
+       struct tb_port *nhi = tunnel->src_port;
+       u32 credits;
+
+       credits = active ? tb_dma_credits(nhi) : 0;
+       return tb_port_set_initial_credits(nhi, credits);
+}
+
+static void tb_dma_init_path(struct tb_path *path, unsigned int isb,
+                            unsigned int efc, u32 credits)
+{
+       int i;
+
+       path->egress_fc_enable = efc;
+       path->ingress_fc_enable = TB_PATH_ALL;
+       path->egress_shared_buffer = TB_PATH_NONE;
+       path->ingress_shared_buffer = isb;
+       path->priority = 5;
+       path->weight = 1;
+       path->clear_fc = true;
+
+       for (i = 0; i < path->path_length; i++)
+               path->hops[i].initial_credits = credits;
+}
+
+/**
+ * tb_tunnel_alloc_dma() - allocate a DMA tunnel
+ * @tb: Pointer to the domain structure
+ * @nhi: Host controller port
+ * @dst: Destination null port which the other domain is connected to
+ * @transmit_ring: NHI ring number used to send packets towards the
+ *                other domain
+ * @transmit_path: HopID used for transmitting packets
+ * @receive_ring: NHI ring number used to receive packets from the
+ *               other domain
+ * @reveive_path: HopID used for receiving packets
+ *
+ * Return: Returns a tb_tunnel on success or NULL on failure.
+ */
+struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
+                                     struct tb_port *dst, int transmit_ring,
+                                     int transmit_path, int receive_ring,
+                                     int receive_path)
+{
+       struct tb_tunnel *tunnel;
+       struct tb_path *path;
+       u32 credits;
+
+       tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_DMA);
+       if (!tunnel)
+               return NULL;
+
+       tunnel->activate = tb_dma_activate;
+       tunnel->src_port = nhi;
+       tunnel->dst_port = dst;
+
+       credits = tb_dma_credits(nhi);
+
+       path = tb_path_alloc(tb, dst, nhi, receive_path, receive_ring, 0);
+       if (!path) {
+               tb_tunnel_free(tunnel);
+               return NULL;
+       }
+       tb_dma_init_path(path, TB_PATH_NONE, TB_PATH_SOURCE | TB_PATH_INTERNAL,
+                        credits);
+       tunnel->paths[TB_DMA_PATH_IN] = path;
+
+       path = tb_path_alloc(tb, nhi, dst, transmit_ring, transmit_path, 0);
+       if (!path) {
+               tb_tunnel_free(tunnel);
+               return NULL;
+       }
+       tb_dma_init_path(path, TB_PATH_SOURCE, TB_PATH_ALL, credits);
+       tunnel->paths[TB_DMA_PATH_OUT] = path;
+
+       return tunnel;
+}
+
 /**
  * tb_tunnel_free() - free a tunnel
  * @tunnel: Tunnel to be freed
diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h
index 07583f8247c1..fa51217e5925 100644
--- a/drivers/thunderbolt/tunnel.h
+++ b/drivers/thunderbolt/tunnel.h
@@ -14,6 +14,7 @@
 enum tb_tunnel_type {
        TB_TUNNEL_PCI,
        TB_TUNNEL_DP,
+       TB_TUNNEL_DMA,
 };
 
 /**
@@ -44,6 +45,10 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct 
tb_port *up,
 struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in);
 struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
                                     struct tb_port *out);
+struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
+                                     struct tb_port *dst, int transmit_ring,
+                                     int transmit_path, int receive_ring,
+                                     int receive_path);
 
 void tb_tunnel_free(struct tb_tunnel *tunnel);
 int tb_tunnel_activate(struct tb_tunnel *tunnel);
@@ -61,5 +66,10 @@ static inline bool tb_tunnel_is_dp(const struct tb_tunnel 
*tunnel)
        return tunnel->type == TB_TUNNEL_DP;
 }
 
+static inline bool tb_tunnel_is_dma(const struct tb_tunnel *tunnel)
+{
+       return tunnel->type == TB_TUNNEL_DMA;
+}
+
 #endif
 
-- 
2.20.1

Reply via email to