These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / net / wireless / ath / wil6210 / txrx.c
index e8bd512..3bc9bc0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2014 Qualcomm Atheros, Inc.
+ * Copyright (c) 2012-2015 Qualcomm Atheros, Inc.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -236,7 +236,7 @@ static int wil_vring_alloc_skb(struct wil6210_priv *wil, struct vring *vring,
                return -ENOMEM;
        }
 
-       d->dma.d0 = BIT(9) | RX_DMA_D0_CMD_DMA_IT;
+       d->dma.d0 = RX_DMA_D0_CMD_DMA_RT | RX_DMA_D0_CMD_DMA_IT;
        wil_desc_addr_set(&d->dma.addr, pa);
        /* ip_length don't care */
        /* b11 don't care */
@@ -358,6 +358,13 @@ static void wil_rx_add_radiotap_header(struct wil6210_priv *wil,
        }
 }
 
+/* similar to ieee80211_ version, but FC contain only 1-st byte */
+static inline int wil_is_back_req(u8 fc)
+{
+       return (fc & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) ==
+              (IEEE80211_FTYPE_CTL | IEEE80211_STYPE_BACK_REQ);
+}
+
 /**
  * reap 1 frame from @swhead
  *
@@ -379,14 +386,16 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil,
        u16 dmalen;
        u8 ftype;
        int cid;
-       int i = (int)vring->swhead;
+       int i;
        struct wil_net_stats *stats;
 
        BUILD_BUG_ON(sizeof(struct vring_rx_desc) > sizeof(skb->cb));
 
+again:
        if (unlikely(wil_vring_is_empty(vring)))
                return NULL;
 
+       i = (int)vring->swhead;
        _d = &vring->va[i].rx;
        if (unlikely(!(_d->dma.status & RX_DMA_STATUS_DU))) {
                /* it is not error, we just reached end of Rx done area */
@@ -398,7 +407,7 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil,
        wil_vring_advance_head(vring, 1);
        if (!skb) {
                wil_err(wil, "No Rx skb at [%d]\n", i);
-               return NULL;
+               goto again;
        }
        d = wil_skb_rxdesc(skb);
        *d = *_d;
@@ -409,13 +418,17 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil,
 
        trace_wil6210_rx(i, d);
        wil_dbg_txrx(wil, "Rx[%3d] : %d bytes\n", i, dmalen);
-       wil_hex_dump_txrx("Rx ", DUMP_PREFIX_NONE, 32, 4,
+       wil_hex_dump_txrx("RxD ", DUMP_PREFIX_NONE, 32, 4,
                          (const void *)d, sizeof(*d), false);
 
+       cid = wil_rxdesc_cid(d);
+       stats = &wil->sta[cid].stats;
+
        if (unlikely(dmalen > sz)) {
                wil_err(wil, "Rx size too large: %d bytes!\n", dmalen);
+               stats->rx_large_frame++;
                kfree_skb(skb);
-               return NULL;
+               goto again;
        }
        skb_trim(skb, dmalen);
 
@@ -424,9 +437,9 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil,
        wil_hex_dump_txrx("Rx ", DUMP_PREFIX_OFFSET, 16, 1,
                          skb->data, skb_headlen(skb), false);
 
-       cid = wil_rxdesc_cid(d);
-       stats = &wil->sta[cid].stats;
        stats->last_mcs_rx = wil_rxdesc_mcs(d);
+       if (stats->last_mcs_rx < ARRAY_SIZE(stats->rx_per_mcs))
+               stats->rx_per_mcs[stats->last_mcs_rx]++;
 
        /* use radiotap header only if required */
        if (ndev->type == ARPHRD_IEEE80211_RADIOTAP)
@@ -435,24 +448,47 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil,
        /* no extra checks if in sniffer mode */
        if (ndev->type != ARPHRD_ETHER)
                return skb;
-       /*
-        * Non-data frames may be delivered through Rx DMA channel (ex: BAR)
+       /* Non-data frames may be delivered through Rx DMA channel (ex: BAR)
         * Driver should recognize it by frame type, that is found
         * in Rx descriptor. If type is not data, it is 802.11 frame as is
         */
        ftype = wil_rxdesc_ftype(d) << 2;
        if (unlikely(ftype != IEEE80211_FTYPE_DATA)) {
-               wil_dbg_txrx(wil, "Non-data frame ftype 0x%08x\n", ftype);
-               /* TODO: process it */
+               u8 fc1 = wil_rxdesc_fc1(d);
+               int mid = wil_rxdesc_mid(d);
+               int tid = wil_rxdesc_tid(d);
+               u16 seq = wil_rxdesc_seq(d);
+
+               wil_dbg_txrx(wil,
+                            "Non-data frame FC[7:0] 0x%02x MID %d CID %d TID %d Seq 0x%03x\n",
+                            fc1, mid, cid, tid, seq);
+               stats->rx_non_data_frame++;
+               if (wil_is_back_req(fc1)) {
+                       wil_dbg_txrx(wil,
+                                    "BAR: MID %d CID %d TID %d Seq 0x%03x\n",
+                                    mid, cid, tid, seq);
+                       wil_rx_bar(wil, cid, tid, seq);
+               } else {
+                       /* print again all info. One can enable only this
+                        * without overhead for printing every Rx frame
+                        */
+                       wil_dbg_txrx(wil,
+                                    "Unhandled non-data frame FC[7:0] 0x%02x MID %d CID %d TID %d Seq 0x%03x\n",
+                                    fc1, mid, cid, tid, seq);
+                       wil_hex_dump_txrx("RxD ", DUMP_PREFIX_NONE, 32, 4,
+                                         (const void *)d, sizeof(*d), false);
+                       wil_hex_dump_txrx("Rx ", DUMP_PREFIX_OFFSET, 16, 1,
+                                         skb->data, skb_headlen(skb), false);
+               }
                kfree_skb(skb);
-               return NULL;
+               goto again;
        }
 
        if (unlikely(skb->len < ETH_HLEN + snaplen)) {
                wil_err(wil, "Short frame, len = %d\n", skb->len);
-               /* TODO: process it (i.e. BAR) */
+               stats->rx_short_frame++;
                kfree_skb(skb);
-               return NULL;
+               goto again;
        }
 
        /* L4 IDENT is on when HW calculated checksum, check status
@@ -507,7 +543,7 @@ static int wil_rx_refill(struct wil6210_priv *wil, int count)
                        break;
                }
        }
-       iowrite32(v->swtail, wil->csr + HOSTADDR(v->hwtail));
+       wil_w(wil, v->hwtail, v->swtail);
 
        return rc;
 }
@@ -539,6 +575,14 @@ void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev)
                [GRO_DROP]              = "GRO_DROP",
        };
 
+       if (ndev->features & NETIF_F_RXHASH)
+               /* fake L4 to ensure it won't be re-calculated later
+                * set hash to any non-zero value to activate rps
+                * mechanism, core will be chosen according
+                * to user-level rps configuration.
+                */
+               skb_set_hash(skb, 1, PKT_HASH_TYPE_L4);
+
        skb_orphan(skb);
 
        if (wdev->iftype == NL80211_IFTYPE_AP && !wil->ap_isolate) {
@@ -724,6 +768,8 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size,
 
        cmd.vring_cfg.tx_sw_ring.ring_mem_base = cpu_to_le64(vring->pa);
 
+       if (!wil->privacy)
+               txdata->dot1x_open = true;
        rc = wmi_call(wil, WMI_VRING_CFG_CMDID, &cmd, sizeof(cmd),
                      WMI_VRING_CFG_DONE_EVENTID, &reply, sizeof(reply), 100);
        if (rc)
@@ -738,11 +784,13 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size,
        vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr);
 
        txdata->enabled = 1;
-       if (wil->sta[cid].data_port_open && (agg_wsize >= 0))
+       if (txdata->dot1x_open && (agg_wsize >= 0))
                wil_addba_tx_request(wil, id, agg_wsize);
 
        return 0;
  out_free:
+       txdata->dot1x_open = false;
+       txdata->enabled = 0;
        wil_vring_free(wil, vring, 1);
  out:
 
@@ -792,6 +840,8 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size)
 
        cmd.vring_cfg.tx_sw_ring.ring_mem_base = cpu_to_le64(vring->pa);
 
+       if (!wil->privacy)
+               txdata->dot1x_open = true;
        rc = wmi_call(wil, WMI_BCAST_VRING_CFG_CMDID, &cmd, sizeof(cmd),
                      WMI_VRING_CFG_DONE_EVENTID, &reply, sizeof(reply), 100);
        if (rc)
@@ -809,6 +859,8 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size)
 
        return 0;
  out_free:
+       txdata->enabled = 0;
+       txdata->dot1x_open = false;
        wil_vring_free(wil, vring, 1);
  out:
 
@@ -828,6 +880,7 @@ void wil_vring_fini_tx(struct wil6210_priv *wil, int id)
        wil_dbg_misc(wil, "%s() id=%d\n", __func__, id);
 
        spin_lock_bh(&txdata->lock);
+       txdata->dot1x_open = false;
        txdata->enabled = 0; /* no Tx can be in progress or start anew */
        spin_unlock_bh(&txdata->lock);
        /* make sure NAPI won't touch this vring */
@@ -848,12 +901,11 @@ static struct vring *wil_find_tx_ucast(struct wil6210_priv *wil,
        if (cid < 0)
                return NULL;
 
-       if (!wil->sta[cid].data_port_open &&
-           (skb->protocol != cpu_to_be16(ETH_P_PAE)))
-               return NULL;
-
        /* TODO: fix for multiple TID */
        for (i = 0; i < ARRAY_SIZE(wil->vring2cid_tid); i++) {
+               if (!wil->vring_tx_data[i].dot1x_open &&
+                   (skb->protocol != cpu_to_be16(ETH_P_PAE)))
+                       continue;
                if (wil->vring2cid_tid[i][0] == cid) {
                        struct vring *v = &wil->vring_tx[i];
 
@@ -883,7 +935,7 @@ static struct vring *wil_find_tx_vring_sta(struct wil6210_priv *wil,
 
        /* In the STA mode, it is expected to have only 1 VRING
         * for the AP we connected to.
-        * find 1-st vring and see whether it is eligible for data
+        * find 1-st vring eligible for this skb and use it.
         */
        for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) {
                v = &wil->vring_tx[i];
@@ -894,9 +946,9 @@ static struct vring *wil_find_tx_vring_sta(struct wil6210_priv *wil,
                if (cid >= WIL6210_MAX_CID) /* skip BCAST */
                        continue;
 
-               if (!wil->sta[cid].data_port_open &&
+               if (!wil->vring_tx_data[i].dot1x_open &&
                    (skb->protocol != cpu_to_be16(ETH_P_PAE)))
-                       break;
+                       continue;
 
                wil_dbg_txrx(wil, "Tx -> ring %d\n", i);
 
@@ -918,7 +970,6 @@ static struct vring *wil_find_tx_vring_sta(struct wil6210_priv *wil,
  *    in all cases override dest address to unicast peer's address
  * Use old strategy when new is not supported yet:
  *  - for PBSS
- *  - for secure link
  */
 static struct vring *wil_find_tx_bcast_1(struct wil6210_priv *wil,
                                         struct sk_buff *skb)
@@ -931,6 +982,9 @@ static struct vring *wil_find_tx_bcast_1(struct wil6210_priv *wil,
        v = &wil->vring_tx[i];
        if (!v->va)
                return NULL;
+       if (!wil->vring_tx_data[i].dot1x_open &&
+           (skb->protocol != cpu_to_be16(ETH_P_PAE)))
+               return NULL;
 
        return v;
 }
@@ -963,7 +1017,8 @@ static struct vring *wil_find_tx_bcast_2(struct wil6210_priv *wil,
                cid = wil->vring2cid_tid[i][0];
                if (cid >= WIL6210_MAX_CID) /* skip BCAST */
                        continue;
-               if (!wil->sta[cid].data_port_open)
+               if (!wil->vring_tx_data[i].dot1x_open &&
+                   (skb->protocol != cpu_to_be16(ETH_P_PAE)))
                        continue;
 
                /* don't Tx back to source when re-routing Rx->Tx at the AP */
@@ -989,7 +1044,8 @@ found:
                cid = wil->vring2cid_tid[i][0];
                if (cid >= WIL6210_MAX_CID) /* skip BCAST */
                        continue;
-               if (!wil->sta[cid].data_port_open)
+               if (!wil->vring_tx_data[i].dot1x_open &&
+                   (skb->protocol != cpu_to_be16(ETH_P_PAE)))
                        continue;
 
                if (0 == memcmp(wil->sta[cid].addr, src, ETH_ALEN))
@@ -1016,9 +1072,6 @@ static struct vring *wil_find_tx_bcast(struct wil6210_priv *wil,
        if (wdev->iftype != NL80211_IFTYPE_AP)
                return wil_find_tx_bcast_2(wil, skb);
 
-       if (wil->privacy)
-               return wil_find_tx_bcast_2(wil, skb);
-
        return wil_find_tx_bcast_1(wil, skb);
 }
 
@@ -1047,14 +1100,52 @@ static int wil_tx_desc_map(struct vring_tx_desc *d, dma_addr_t pa, u32 len,
 static inline
 void wil_tx_desc_set_nr_frags(struct vring_tx_desc *d, int nr_frags)
 {
-       d->mac.d[2] |= ((nr_frags + 1) <<
-                      MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS);
+       d->mac.d[2] |= (nr_frags << MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS);
 }
 
-static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
-                                        struct vring_tx_desc *d,
-                                        struct sk_buff *skb)
+/**
+ * Sets the descriptor @d up for csum and/or TSO offloading. The corresponding
+ * @skb is used to obtain the protocol and headers length.
+ * @tso_desc_type is a descriptor type for TSO: 0 - a header, 1 - first data,
+ * 2 - middle, 3 - last descriptor.
+ */
+
+static void wil_tx_desc_offload_setup_tso(struct vring_tx_desc *d,
+                                         struct sk_buff *skb,
+                                         int tso_desc_type, bool is_ipv4,
+                                         int tcp_hdr_len, int skb_net_hdr_len)
 {
+       d->dma.b11 = ETH_HLEN; /* MAC header length */
+       d->dma.b11 |= is_ipv4 << DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS;
+
+       d->dma.d0 |= (2 << DMA_CFG_DESC_TX_0_L4_TYPE_POS);
+       /* L4 header len: TCP header length */
+       d->dma.d0 |= (tcp_hdr_len & DMA_CFG_DESC_TX_0_L4_LENGTH_MSK);
+
+       /* Setup TSO: bit and desc type */
+       d->dma.d0 |= (BIT(DMA_CFG_DESC_TX_0_TCP_SEG_EN_POS)) |
+               (tso_desc_type << DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS);
+       d->dma.d0 |= (is_ipv4 << DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_POS);
+
+       d->dma.ip_length = skb_net_hdr_len;
+       /* Enable TCP/UDP checksum */
+       d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_POS);
+       /* Calculate pseudo-header */
+       d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_POS);
+}
+
+/**
+ * Sets the descriptor @d up for csum. The corresponding
+ * @skb is used to obtain the protocol and headers length.
+ * Returns the protocol: 0 - not TCP, 1 - TCPv4, 2 - TCPv6.
+ * Note, if d==NULL, the function only returns the protocol result.
+ *
+ * It is very similar to previous wil_tx_desc_offload_setup_tso. This
+ * is "if unrolling" to optimize the critical path.
+ */
+
+static int wil_tx_desc_offload_setup(struct vring_tx_desc *d,
+                                    struct sk_buff *skb){
        int protocol;
 
        if (skb->ip_summed != CHECKSUM_PARTIAL)
@@ -1099,6 +1190,305 @@ static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil,
        return 0;
 }
 
+static inline void wil_tx_last_desc(struct vring_tx_desc *d)
+{
+       d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS) |
+             BIT(DMA_CFG_DESC_TX_0_CMD_MARK_WB_POS) |
+             BIT(DMA_CFG_DESC_TX_0_CMD_DMA_IT_POS);
+}
+
+static inline void wil_set_tx_desc_last_tso(volatile struct vring_tx_desc *d)
+{
+       d->dma.d0 |= wil_tso_type_lst <<
+                 DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS;
+}
+
+static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct vring *vring,
+                             struct sk_buff *skb)
+{
+       struct device *dev = wil_to_dev(wil);
+
+       /* point to descriptors in shared memory */
+       volatile struct vring_tx_desc *_desc = NULL, *_hdr_desc,
+                                     *_first_desc = NULL;
+
+       /* pointers to shadow descriptors */
+       struct vring_tx_desc desc_mem, hdr_desc_mem, first_desc_mem,
+                            *d = &hdr_desc_mem, *hdr_desc = &hdr_desc_mem,
+                            *first_desc = &first_desc_mem;
+
+       /* pointer to shadow descriptors' context */
+       struct wil_ctx *hdr_ctx, *first_ctx = NULL;
+
+       int descs_used = 0; /* total number of used descriptors */
+       int sg_desc_cnt = 0; /* number of descriptors for current mss*/
+
+       u32 swhead = vring->swhead;
+       int used, avail = wil_vring_avail_tx(vring);
+       int nr_frags = skb_shinfo(skb)->nr_frags;
+       int min_desc_required = nr_frags + 1;
+       int mss = skb_shinfo(skb)->gso_size;    /* payload size w/o headers */
+       int f, len, hdrlen, headlen;
+       int vring_index = vring - wil->vring_tx;
+       struct vring_tx_data *txdata = &wil->vring_tx_data[vring_index];
+       uint i = swhead;
+       dma_addr_t pa;
+       const skb_frag_t *frag = NULL;
+       int rem_data = mss;
+       int lenmss;
+       int hdr_compensation_need = true;
+       int desc_tso_type = wil_tso_type_first;
+       bool is_ipv4;
+       int tcp_hdr_len;
+       int skb_net_hdr_len;
+       int gso_type;
+       int rc = -EINVAL;
+
+       wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n",
+                    __func__, skb->len, vring_index);
+
+       if (unlikely(!txdata->enabled))
+               return -EINVAL;
+
+       /* A typical page 4K is 3-4 payloads, we assume each fragment
+        * is a full payload, that's how min_desc_required has been
+        * calculated. In real we might need more or less descriptors,
+        * this is the initial check only.
+        */
+       if (unlikely(avail < min_desc_required)) {
+               wil_err_ratelimited(wil,
+                                   "TSO: Tx ring[%2d] full. No space for %d fragments\n",
+                                   vring_index, min_desc_required);
+               return -ENOMEM;
+       }
+
+       /* Header Length = MAC header len + IP header len + TCP header len*/
+       hdrlen = ETH_HLEN +
+               (int)skb_network_header_len(skb) +
+               tcp_hdrlen(skb);
+
+       gso_type = skb_shinfo(skb)->gso_type & (SKB_GSO_TCPV6 | SKB_GSO_TCPV4);
+       switch (gso_type) {
+       case SKB_GSO_TCPV4:
+               /* TCP v4, zero out the IP length and IPv4 checksum fields
+                * as required by the offloading doc
+                */
+               ip_hdr(skb)->tot_len = 0;
+               ip_hdr(skb)->check = 0;
+               is_ipv4 = true;
+               break;
+       case SKB_GSO_TCPV6:
+               /* TCP v6, zero out the payload length */
+               ipv6_hdr(skb)->payload_len = 0;
+               is_ipv4 = false;
+               break;
+       default:
+               /* other than TCPv4 or TCPv6 types are not supported for TSO.
+                * It is also illegal for both to be set simultaneously
+                */
+               return -EINVAL;
+       }
+
+       if (skb->ip_summed != CHECKSUM_PARTIAL)
+               return -EINVAL;
+
+       /* tcp header length and skb network header length are fixed for all
+        * packet's descriptors - read then once here
+        */
+       tcp_hdr_len = tcp_hdrlen(skb);
+       skb_net_hdr_len = skb_network_header_len(skb);
+
+       _hdr_desc = &vring->va[i].tx;
+
+       pa = dma_map_single(dev, skb->data, hdrlen, DMA_TO_DEVICE);
+       if (unlikely(dma_mapping_error(dev, pa))) {
+               wil_err(wil, "TSO: Skb head DMA map error\n");
+               goto err_exit;
+       }
+
+       wil_tx_desc_map(hdr_desc, pa, hdrlen, vring_index);
+       wil_tx_desc_offload_setup_tso(hdr_desc, skb, wil_tso_type_hdr, is_ipv4,
+                                     tcp_hdr_len, skb_net_hdr_len);
+       wil_tx_last_desc(hdr_desc);
+
+       vring->ctx[i].mapped_as = wil_mapped_as_single;
+       hdr_ctx = &vring->ctx[i];
+
+       descs_used++;
+       headlen = skb_headlen(skb) - hdrlen;
+
+       for (f = headlen ? -1 : 0; f < nr_frags; f++)  {
+               if (headlen) {
+                       len = headlen;
+                       wil_dbg_txrx(wil, "TSO: process skb head, len %u\n",
+                                    len);
+               } else {
+                       frag = &skb_shinfo(skb)->frags[f];
+                       len = frag->size;
+                       wil_dbg_txrx(wil, "TSO: frag[%d]: len %u\n", f, len);
+               }
+
+               while (len) {
+                       wil_dbg_txrx(wil,
+                                    "TSO: len %d, rem_data %d, descs_used %d\n",
+                                    len, rem_data, descs_used);
+
+                       if (descs_used == avail)  {
+                               wil_err_ratelimited(wil, "TSO: ring overflow\n");
+                               rc = -ENOMEM;
+                               goto mem_error;
+                       }
+
+                       lenmss = min_t(int, rem_data, len);
+                       i = (swhead + descs_used) % vring->size;
+                       wil_dbg_txrx(wil, "TSO: lenmss %d, i %d\n", lenmss, i);
+
+                       if (!headlen) {
+                               pa = skb_frag_dma_map(dev, frag,
+                                                     frag->size - len, lenmss,
+                                                     DMA_TO_DEVICE);
+                               vring->ctx[i].mapped_as = wil_mapped_as_page;
+                       } else {
+                               pa = dma_map_single(dev,
+                                                   skb->data +
+                                                   skb_headlen(skb) - headlen,
+                                                   lenmss,
+                                                   DMA_TO_DEVICE);
+                               vring->ctx[i].mapped_as = wil_mapped_as_single;
+                               headlen -= lenmss;
+                       }
+
+                       if (unlikely(dma_mapping_error(dev, pa))) {
+                               wil_err(wil, "TSO: DMA map page error\n");
+                               goto mem_error;
+                       }
+
+                       _desc = &vring->va[i].tx;
+
+                       if (!_first_desc) {
+                               _first_desc = _desc;
+                               first_ctx = &vring->ctx[i];
+                               d = first_desc;
+                       } else {
+                               d = &desc_mem;
+                       }
+
+                       wil_tx_desc_map(d, pa, lenmss, vring_index);
+                       wil_tx_desc_offload_setup_tso(d, skb, desc_tso_type,
+                                                     is_ipv4, tcp_hdr_len,
+                                                     skb_net_hdr_len);
+
+                       /* use tso_type_first only once */
+                       desc_tso_type = wil_tso_type_mid;
+
+                       descs_used++;  /* desc used so far */
+                       sg_desc_cnt++; /* desc used for this segment */
+                       len -= lenmss;
+                       rem_data -= lenmss;
+
+                       wil_dbg_txrx(wil,
+                                    "TSO: len %d, rem_data %d, descs_used %d, sg_desc_cnt %d,\n",
+                                    len, rem_data, descs_used, sg_desc_cnt);
+
+                       /* Close the segment if reached mss size or last frag*/
+                       if (rem_data == 0 || (f == nr_frags - 1 && len == 0)) {
+                               if (hdr_compensation_need) {
+                                       /* first segment include hdr desc for
+                                        * release
+                                        */
+                                       hdr_ctx->nr_frags = sg_desc_cnt;
+                                       wil_tx_desc_set_nr_frags(first_desc,
+                                                                sg_desc_cnt +
+                                                                1);
+                                       hdr_compensation_need = false;
+                               } else {
+                                       wil_tx_desc_set_nr_frags(first_desc,
+                                                                sg_desc_cnt);
+                               }
+                               first_ctx->nr_frags = sg_desc_cnt - 1;
+
+                               wil_tx_last_desc(d);
+
+                               /* first descriptor may also be the last
+                                * for this mss - make sure not to copy
+                                * it twice
+                                */
+                               if (first_desc != d)
+                                       *_first_desc = *first_desc;
+
+                               /*last descriptor will be copied at the end
+                                * of this TS processing
+                                */
+                               if (f < nr_frags - 1 || len > 0)
+                                       *_desc = *d;
+
+                               rem_data = mss;
+                               _first_desc = NULL;
+                               sg_desc_cnt = 0;
+                       } else if (first_desc != d) /* update mid descriptor */
+                                       *_desc = *d;
+               }
+       }
+
+       /* first descriptor may also be the last.
+        * in this case d pointer is invalid
+        */
+       if (_first_desc == _desc)
+               d = first_desc;
+
+       /* Last data descriptor */
+       wil_set_tx_desc_last_tso(d);
+       *_desc = *d;
+
+       /* Fill the total number of descriptors in first desc (hdr)*/
+       wil_tx_desc_set_nr_frags(hdr_desc, descs_used);
+       *_hdr_desc = *hdr_desc;
+
+       /* hold reference to skb
+        * to prevent skb release before accounting
+        * in case of immediate "tx done"
+        */
+       vring->ctx[i].skb = skb_get(skb);
+
+       /* performance monitoring */
+       used = wil_vring_used_tx(vring);
+       if (wil_val_in_range(vring_idle_trsh,
+                            used, used + descs_used)) {
+               txdata->idle += get_cycles() - txdata->last_idle;
+               wil_dbg_txrx(wil,  "Ring[%2d] not idle %d -> %d\n",
+                            vring_index, used, used + descs_used);
+       }
+
+       /* advance swhead */
+       wil_vring_advance_head(vring, descs_used);
+       wil_dbg_txrx(wil, "TSO: Tx swhead %d -> %d\n", swhead, vring->swhead);
+
+       /* make sure all writes to descriptors (shared memory) are done before
+        * committing them to HW
+        */
+       wmb();
+
+       wil_w(wil, vring->hwtail, vring->swhead);
+       return 0;
+
+mem_error:
+       while (descs_used > 0) {
+               struct wil_ctx *ctx;
+
+               i = (swhead + descs_used) % vring->size;
+               d = (struct vring_tx_desc *)&vring->va[i].tx;
+               _desc = &vring->va[i].tx;
+               *d = *_desc;
+               _desc->dma.status = TX_DMA_STATUS_DU;
+               ctx = &vring->ctx[i];
+               wil_txdesc_unmap(dev, d, ctx);
+               memset(ctx, 0, sizeof(*ctx));
+               descs_used--;
+       }
+err_exit:
+       return rc;
+}
+
 static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
                          struct sk_buff *skb)
 {
@@ -1117,7 +1507,8 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
        bool mcast = (vring_index == wil->bcast_vring);
        uint len = skb_headlen(skb);
 
-       wil_dbg_txrx(wil, "%s()\n", __func__);
+       wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n",
+                    __func__, skb->len, vring_index);
 
        if (unlikely(!txdata->enabled))
                return -EINVAL;
@@ -1144,23 +1535,18 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
        wil_tx_desc_map(d, pa, len, vring_index);
        if (unlikely(mcast)) {
                d->mac.d[0] |= BIT(MAC_CFG_DESC_TX_0_MCS_EN_POS); /* MCS 0 */
-               if (unlikely(len > WIL_BCAST_MCS0_LIMIT)) {
-                       /* set MCS 1 */
+               if (unlikely(len > WIL_BCAST_MCS0_LIMIT)) /* set MCS 1 */
                        d->mac.d[0] |= (1 << MAC_CFG_DESC_TX_0_MCS_INDEX_POS);
-                       /* packet mode 2 */
-                       d->mac.d[1] |= BIT(MAC_CFG_DESC_TX_1_PKT_MODE_EN_POS) |
-                                      (2 << MAC_CFG_DESC_TX_1_PKT_MODE_POS);
-               }
        }
        /* Process TCP/UDP checksum offloading */
-       if (unlikely(wil_tx_desc_offload_cksum_set(wil, d, skb))) {
+       if (unlikely(wil_tx_desc_offload_setup(d, skb))) {
                wil_err(wil, "Tx[%2d] Failed to set cksum, drop packet\n",
                        vring_index);
                goto dma_error;
        }
 
        vring->ctx[i].nr_frags = nr_frags;
-       wil_tx_desc_set_nr_frags(d, nr_frags);
+       wil_tx_desc_set_nr_frags(d, nr_frags + 1);
 
        /* middle segments */
        for (; f < nr_frags; f++) {
@@ -1176,15 +1562,18 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
                _d = &vring->va[i].tx;
                pa = skb_frag_dma_map(dev, frag, 0, skb_frag_size(frag),
                                      DMA_TO_DEVICE);
-               if (unlikely(dma_mapping_error(dev, pa)))
+               if (unlikely(dma_mapping_error(dev, pa))) {
+                       wil_err(wil, "Tx[%2d] failed to map fragment\n",
+                               vring_index);
                        goto dma_error;
+               }
                vring->ctx[i].mapped_as = wil_mapped_as_page;
                wil_tx_desc_map(d, pa, len, vring_index);
                /* no need to check return code -
                 * if it succeeded for 1-st descriptor,
                 * it will succeed here too
                 */
-               wil_tx_desc_offload_cksum_set(wil, d, skb);
+               wil_tx_desc_offload_setup(d, skb);
        }
        /* for the last seg only */
        d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS);
@@ -1215,7 +1604,13 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
        wil_dbg_txrx(wil, "Tx[%2d] swhead %d -> %d\n", vring_index, swhead,
                     vring->swhead);
        trace_wil6210_tx(vring_index, swhead, skb->len, nr_frags);
-       iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail));
+
+       /* make sure all writes to descriptors (shared memory) are done before
+        * committing them to HW
+        */
+       wmb();
+
+       wil_w(wil, vring->hwtail, vring->swhead);
 
        return 0;
  dma_error:
@@ -1231,9 +1626,6 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
                _d->dma.status = TX_DMA_STATUS_DU;
                wil_txdesc_unmap(dev, d, ctx);
 
-               if (ctx->skb)
-                       dev_kfree_skb_any(ctx->skb);
-
                memset(ctx, 0, sizeof(*ctx));
        }
 
@@ -1248,8 +1640,12 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring,
        int rc;
 
        spin_lock(&txdata->lock);
-       rc = __wil_tx_vring(wil, vring, skb);
+
+       rc = (skb_is_gso(skb) ? __wil_tx_vring_tso : __wil_tx_vring)
+            (wil, vring, skb);
+
        spin_unlock(&txdata->lock);
+
        return rc;
 }
 
@@ -1271,7 +1667,7 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev)
                goto drop;
        }
        if (unlikely(!test_bit(wil_status_fwconnected, wil->status))) {
-               wil_err(wil, "FW not connected\n");
+               wil_err_ratelimited(wil, "FW not connected\n");
                goto drop;
        }
        if (unlikely(wil->wdev->iftype == NL80211_IFTYPE_MONITOR)) {
@@ -1376,7 +1772,8 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid)
                struct wil_ctx *ctx = &vring->ctx[vring->swtail];
                /**
                 * For the fragmented skb, HW will set DU bit only for the
-                * last fragment. look for it
+                * last fragment. look for it.
+                * In TSO the first DU will include hdr desc
                 */
                int lf = (vring->swtail + ctx->nr_frags) % vring->size;
                /* TODO: check we are not past head */