Browse Source

qcacmn: Fix a race btw tx and tx-compl

While handling a multi-segment TSO packet, there is a race condition
where, if tx complete arrives fast enough, the un-sent TSO segments
may be lost forever and a previously sent segment would be attempted
to be sent over.

Fix the race condition. Dont use the entry after send to go to
next entry.

Change-Id: I023587a48bea6f3b58aac62e398cc15939bbb773
CRs-Fixed: 2174715
Orhan K AKYILDIZ 7 years ago
parent
commit
c6b4a7be50
3 changed files with 118 additions and 23 deletions
  1. 81 14
      qdf/inc/qdf_trace.h
  2. 25 7
      qdf/inc/qdf_types.h
  3. 12 2
      qdf/linux/src/qdf_nbuf.c

+ 81 - 14
qdf/inc/qdf_trace.h

@@ -611,29 +611,93 @@ void __printf(3, 4) qdf_snprintf(char *str_buffer, unsigned int size,
 #define QDF_SNPRINTF qdf_snprintf
 
 #ifdef TSOSEG_DEBUG
+
+static inline void qdf_tso_seg_dbg_bug(char *msg)
+{
+	qdf_print(msg);
+	QDF_BUG(0);
+};
+
+/**
+ * qdf_tso_seg_dbg_init - initialize TSO segment debug structure
+ * @tsoseg : structure to initialize
+ *
+ * TSO segment dbg structures are attached to qdf_tso_seg_elem_t
+ * structures and are allocated only of TSOSEG_DEBUG is defined.
+ * When allocated, at the time of the tso_seg_pool initialization,
+ * which goes with tx_desc initialization (1:1), each structure holds
+ * a number of (currently 16) history entries, basically describing
+ * what operation has been performed on this particular tso_seg_elem.
+ * This history buffer is a circular buffer and the current index is
+ * held in an atomic variable called cur. It is incremented every
+ * operation. Each of these operations are added with the function
+ * qdf_tso_seg_dbg_record.
+ * For each segment, this initialization function MUST be called PRIOR
+ * TO any _dbg_record() function calls.
+ * On free, qdf_tso_seg_elem structure is cleared (using qdf_tso_seg_dbg_zero)
+ * which clears the tso_desc, BUT DOES NOT CLEAR THE HISTORY element.
+ *
+ * Return:
+ *   None
+ */
 static inline
-int qdf_tso_seg_dbg_record(struct qdf_tso_seg_elem_t *tsoseg,
-			   uint16_t caller)
+void qdf_tso_seg_dbg_init(struct qdf_tso_seg_elem_t *tsoseg)
+{
+	tsoseg->dbg.txdesc = NULL;
+	qdf_atomic_init(&tsoseg->dbg.cur); /* history empty */
+}
+
+/**
+ * qdf_tso_seg_dbg_record - add a history entry to TSO debug structure
+ * @tsoseg : structure to initialize
+ * @id     : operation ID (identifies the caller)
+ *
+ * Adds a history entry to the history circular buffer. Each entry
+ * contains an operation id (caller, as currently each ID is used only
+ * once in the source, so it directly identifies the src line that invoked
+ * the recording.
+ *
+ * qdf_tso_seg_dbg_record CAN ONLY BE CALLED AFTER the entry is initialized
+ * by qdf_tso_seg_dbg_init.
+ *
+ * The entry to be added is written at the location pointed by the atomic
+ * variable called cur. Cur is an ever increasing atomic variable. It is
+ * masked so that only the lower 4 bits are used (16 history entries).
+ *
+ * Return:
+ *   int: the entry this record was recorded at
+ */
+static inline
+int qdf_tso_seg_dbg_record(struct qdf_tso_seg_elem_t *tsoseg, short id)
 {
 	int rc = -1;
+	unsigned int c;
+
+	qdf_assert(tsoseg);
 
-	if (tsoseg != NULL) {
-		tsoseg->dbg.cur++;  tsoseg->dbg.cur &= 0x0f;
-		tsoseg->dbg.history[tsoseg->dbg.cur] = caller;
-		rc = tsoseg->dbg.cur;
+	if (id == TSOSEG_LOC_ALLOC) {
+		c = qdf_atomic_read(&tsoseg->dbg.cur);
+		/* dont crash on the very first alloc on the segment */
+		c &= 0x0f;
+		/* allow only INIT and FREE ops before ALLOC */
+		if (tsoseg->dbg.h[c].id >= id)
+			qdf_tso_seg_dbg_bug("Rogue TSO seg alloc");
 	}
+	c = qdf_atomic_inc_return(&tsoseg->dbg.cur);
+
+	c &= 0x0f;
+	tsoseg->dbg.h[c].ts = qdf_get_log_timestamp();
+	tsoseg->dbg.h[c].id = id;
+	rc = c;
+
 	return rc;
 };
-static inline void qdf_tso_seg_dbg_bug(char *msg)
-{
-	qdf_print(msg);
-	QDF_BUG(0);
-};
 
 static inline void
 qdf_tso_seg_dbg_setowner(struct qdf_tso_seg_elem_t *tsoseg, void *owner)
 {
-	tsoseg->dbg.txdesc = owner;
+	if (tsoseg)
+		tsoseg->dbg.txdesc = owner;
 };
 
 static inline void
@@ -645,8 +709,11 @@ qdf_tso_seg_dbg_zero(struct qdf_tso_seg_elem_t *tsoseg)
 
 #else
 static inline
-int qdf_tso_seg_dbg_record(struct qdf_tso_seg_elem_t *tsoseg,
-			   uint16_t caller)
+void qdf_tso_seg_dbg_init(struct qdf_tso_seg_elem_t *tsoseg)
+{
+};
+static inline
+int qdf_tso_seg_dbg_record(struct qdf_tso_seg_elem_t *tsoseg, short id)
 {
 	return 0;
 };

+ 25 - 7
qdf/inc/qdf_types.h

@@ -43,6 +43,9 @@
 /* Include Files */
 #include <i_qdf_types.h>
 #include <stdarg.h>
+#ifdef TSOSEG_DEBUG
+#include <qdf_atomic.h>
+#endif
 
 /* Preprocessor definitions and constants */
 #define QDF_MAX_SGLIST 4
@@ -876,7 +879,7 @@ struct qdf_tso_frag_t {
 };
 
 #define FRAG_NUM_MAX 6
-#define TSO_SEG_MAGIC_COOKIE 0x7EED
+#define TSO_SEG_MAGIC_COOKIE 0x1EED
 
 /**
  * struct qdf_tso_flags_t - TSO specific flags
@@ -956,20 +959,33 @@ enum tsoseg_dbg_caller_e {
 	TSOSEG_LOC_UNDEFINED,
 	TSOSEG_LOC_INIT1,
 	TSOSEG_LOC_INIT2,
+	TSOSEG_LOC_FREE,
+	TSOSEG_LOC_ALLOC,
 	TSOSEG_LOC_DEINIT,
+	TSOSEG_LOC_GETINFO,
+	TSOSEG_LOC_FILLHTTSEG,
+	TSOSEG_LOC_FILLCMNSEG,
 	TSOSEG_LOC_PREPARETSO,
 	TSOSEG_LOC_TXPREPLLFAST,
 	TSOSEG_LOC_UNMAPTSO,
-	TSOSEG_LOC_ALLOC,
-	TSOSEG_LOC_FREE,
+	TSOSEG_LOC_UNMAPLAST,
+	TSOSEG_LOC_FORCE_FREE,
 };
 #ifdef TSOSEG_DEBUG
 
+/**
+ * WARNING: Don't change the history size without changing the wrap
+ *  code in qdf_tso_seg_dbg_record function
+ */
 #define MAX_TSO_SEG_ACT_HISTORY 16
+struct qdf_tso_seg_dbg_history_t {
+	uint64_t ts;
+	short    id;
+};
 struct qdf_tso_seg_dbg_t {
 	void    *txdesc;  /* owner - (ol_txrx_tx_desc_t *) */
-	int      cur;     /* index of last valid entry */
-	uint16_t history[MAX_TSO_SEG_ACT_HISTORY];
+	qdf_atomic_t cur; /* index of last valid entry */
+	struct qdf_tso_seg_dbg_history_t h[MAX_TSO_SEG_ACT_HISTORY];
 };
 #endif /* TSOSEG_DEBUG */
 
@@ -980,8 +996,10 @@ struct qdf_tso_seg_dbg_t {
  */
 struct qdf_tso_seg_elem_t {
 	struct qdf_tso_seg_t seg;
-	uint16_t cookie:15,
-		on_freelist:1;
+	uint32_t cookie:13,
+		on_freelist:1,
+		sent_to_target:1,
+		force_free:1;
 	struct qdf_tso_seg_elem_t *next;
 #ifdef TSOSEG_DEBUG
 	struct qdf_tso_seg_dbg_t dbg;

+ 12 - 2
qdf/linux/src/qdf_nbuf.c

@@ -2836,6 +2836,7 @@ static inline void __qdf_nbuf_fill_tso_cmn_seg_info(
 		   tso_cmn_info->eit_hdr_len,
 		   curr_seg->seg.tso_flags.tcp_seq_num,
 		   curr_seg->seg.total_len);
+	qdf_tso_seg_dbg_record(curr_seg, TSOSEG_LOC_FILLCMNSEG);
 }
 
 /**
@@ -2898,6 +2899,12 @@ uint32_t __qdf_nbuf_get_tso_info(qdf_device_t osdev, struct sk_buff *skb,
 				tso_frag_vaddr, tso_frag_len, DMA_TO_DEVICE);
 	}
 
+	if (unlikely(dma_mapping_error(osdev->dev,
+					tso_frag_paddr))) {
+		qdf_print("%s:%d DMA mapping error!\n", __func__, __LINE__);
+		qdf_assert(0);
+		return 0;
+	}
 	TSO_DEBUG("%s[%d] skb frag len %d tso frag len %d\n", __func__,
 		__LINE__, skb_frag_len, tso_frag_len);
 	num_seg = tso_info->num_segs;
@@ -3024,7 +3031,8 @@ uint32_t __qdf_nbuf_get_tso_info(qdf_device_t osdev, struct sk_buff *skb,
 						 DMA_TO_DEVICE);
 			if (unlikely(dma_mapping_error(osdev->dev,
 							tso_frag_paddr))) {
-				qdf_print("DMA mapping error!\n");
+				qdf_print("%s:%d DMA mapping error!\n",
+						__func__, __LINE__);
 				qdf_assert(0);
 				return 0;
 			}
@@ -3036,6 +3044,7 @@ uint32_t __qdf_nbuf_get_tso_info(qdf_device_t osdev, struct sk_buff *skb,
 		if (!num_seg)
 			curr_seg->seg.tso_flags.fin = tso_cmn_info.tcphdr->fin;
 
+		qdf_tso_seg_dbg_record(curr_seg, TSOSEG_LOC_GETINFO);
 		curr_seg = curr_seg->next;
 	}
 	return tso_info->num_segs;
@@ -3076,13 +3085,13 @@ void __qdf_nbuf_unmap_tso_segment(qdf_device_t osdev,
 			qdf_assert(0);
 			return;
 		}
-		qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_UNMAPTSO);
 		dma_unmap_single(osdev->dev,
 				 tso_seg->seg.tso_frags[num_frags].paddr,
 				 tso_seg->seg.tso_frags[num_frags].length,
 				 __qdf_dma_dir_to_os(QDF_DMA_TO_DEVICE));
 		tso_seg->seg.tso_frags[num_frags].paddr = 0;
 		num_frags--;
+		qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_UNMAPTSO);
 	}
 
 	if (is_last_seg) {
@@ -3097,6 +3106,7 @@ void __qdf_nbuf_unmap_tso_segment(qdf_device_t osdev,
 				 tso_seg->seg.tso_frags[0].length,
 				 __qdf_dma_dir_to_os(QDF_DMA_TO_DEVICE));
 		tso_seg->seg.tso_frags[0].paddr = 0;
+		qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_UNMAPLAST);
 	}
 }
 qdf_export_symbol(__qdf_nbuf_unmap_tso_segment);