Parcourir la source

qcacmn: Do not free timer object from within timer callback

When a timer callback function is being executed, the kernel still has the
timer object data structure. Therefore, do not free the object from within
the timer callback function. Since there are many DFS NOL timers and the
number of DFS NOL timers are not known at the device object creation time
we cannot allocate static memory for DFS NOL timers. They should be
created and freed dynamically. While waiting for the timer to complete
we should not hold the same lock that is held by the timer callback
function, holding the same lock might lead to deadlock.

Instead of freeing the timer object from within the timer we schedule a
workQueue and the timer object is freed from the workQueue callback
function. While destroying the device object or resetting DFS, we need to
stop the timers and free (cleanup) the DFS NOL timer objects that have
already been added. While waiting for a timer to complete
(qdf_timer_sync_cancel) we do not hold the lock that is used by the timer
function callback to protect the list of DFS NOL timers. This is to avoid
deadlock. The list access is protected by the lock so that either the
cleanup thread or the DFS NOL timer removes a timer object.

Also add fix for unwanted channel addition to precac NOL list when precac
is not enabled.

Change-Id: I1ee935fd15a79579197ccccba0e37c6850fde866
CRs-Fixed: 2171259
Abhijit Pradhan il y a 7 ans
Parent
commit
f8e62c0115

+ 15 - 0
umac/dfs/core/src/dfs.h

@@ -664,6 +664,7 @@ struct dfs_state {
  * @nol_next:         Next element pointer.
  */
 struct dfs_nolelem {
+	TAILQ_ENTRY(dfs_nolelem) nolelem_list;
 	struct wlan_dfs *nol_dfs;
 	uint32_t       nol_freq;
 	uint32_t       nol_chwidth;
@@ -890,6 +891,8 @@ struct dfs_event_log {
  * @dfs_seq_num:                     Sequence number.
  * @dfs_nol_event[]:                 NOL event.
  * @dfs_nol_timer:                   NOL list processing.
+ * @dfs_nol_free_list:               NOL free list.
+ * @dfs_nol_elem_free_work:          The work queue to free an NOL element.
  * @dfs_cac_timer:                   CAC timer.
  * @dfs_cac_valid_timer:             Ignore CAC when this timer is running.
  * @dfs_cac_timeout_override:        Overridden cac timeout.
@@ -980,6 +983,10 @@ struct wlan_dfs {
 	uint32_t       dfs_seq_num;
 	int            dfs_nol_event[DFS_CHAN_MAX];
 	os_timer_t     dfs_nol_timer;
+
+	TAILQ_HEAD(, dfs_nolelem) dfs_nol_free_list;
+	qdf_work_t     dfs_nol_elem_free_work;
+
 	os_timer_t     dfs_cac_timer;
 	os_timer_t     dfs_cac_valid_timer;
 	int            dfs_cac_timeout_override;
@@ -1255,6 +1262,14 @@ void dfs_nol_update(struct wlan_dfs *dfs);
  */
 void dfs_nol_timer_cleanup(struct wlan_dfs *dfs);
 
+/**
+ * dfs_nol_workqueue_cleanup() - Flushes NOL workqueue.
+ * @dfs: Pointer to wlan_dfs structure.
+ *
+ * Flushes the NOL workqueue.
+ */
+void dfs_nol_workqueue_cleanup(struct wlan_dfs *dfs);
+
 /**
  * dfs_retain_bin5_burst_pattern() - Retain the BIN5 burst pattern.
  * @dfs: Pointer to wlan_dfs structure.

+ 1 - 0
umac/dfs/core/src/misc/dfs.c

@@ -333,6 +333,7 @@ int dfs_attach(struct wlan_dfs *dfs)
 void dfs_stop(struct wlan_dfs *dfs)
 {
 	dfs_nol_timer_cleanup(dfs);
+	dfs_nol_workqueue_cleanup(dfs);
 	dfs_clear_nolhistory(dfs);
 }
 

+ 57 - 20
umac/dfs/core/src/misc/dfs_nol.c

@@ -94,6 +94,29 @@ static os_timer_func(dfs_nol_timeout)
 	}
 }
 
+/**
+ * dfs_nol_elem_free_work_cb -  Free NOL element
+ *
+ * Free the NOL element memory
+ */
+static void dfs_nol_elem_free_work_cb(void *context)
+{
+	struct wlan_dfs *dfs = (struct wlan_dfs *)context;
+	struct dfs_nolelem *tmp_nol_entry, *nol_entry;
+
+	WLAN_DFSNOL_LOCK(dfs);
+	if (!TAILQ_EMPTY(&dfs->dfs_nol_free_list))
+		TAILQ_FOREACH_SAFE(nol_entry,
+				&dfs->dfs_nol_free_list,
+				nolelem_list,
+				tmp_nol_entry) {
+			TAILQ_REMOVE(&dfs->dfs_nol_free_list,
+					nol_entry, nolelem_list);
+			qdf_mem_free(nol_entry);
+		}
+	WLAN_DFSNOL_UNLOCK(dfs);
+}
+
 void dfs_nol_timer_init(struct wlan_dfs *dfs)
 {
 	qdf_timer_init(NULL,
@@ -107,12 +130,17 @@ void dfs_nol_attach(struct wlan_dfs *dfs)
 {
 	dfs->wlan_dfs_nol_timeout = DFS_NOL_TIMEOUT_S;
 	dfs_nol_timer_init(dfs);
+	qdf_create_work(NULL, &dfs->dfs_nol_elem_free_work,
+			dfs_nol_elem_free_work_cb, dfs);
+	TAILQ_INIT(&dfs->dfs_nol_free_list);
 	dfs->dfs_use_nol = 1;
 	WLAN_DFSNOL_LOCK_CREATE(dfs);
 }
 
 void dfs_nol_detach(struct wlan_dfs *dfs)
 {
+	qdf_flush_work(&dfs->dfs_nol_elem_free_work);
+	qdf_destroy_work(NULL, &dfs->dfs_nol_elem_free_work);
 	WLAN_DFSNOL_LOCK_DESTROY(dfs);
 }
 
@@ -148,8 +176,8 @@ static void dfs_nol_delete(struct wlan_dfs *dfs,
 				nol->nol_chwidth,
 				(qdf_system_ticks_to_msecs
 				 (qdf_system_ticks()) / 1000));
-			qdf_timer_stop(&nol->nol_timer);
-			qdf_mem_free(nol);
+			TAILQ_INSERT_TAIL(&dfs->dfs_nol_free_list,
+						nol, nolelem_list);
 			nol = *prev_next;
 
 			/* Update the NOL counter. */
@@ -200,6 +228,13 @@ static os_timer_func(dfs_remove_from_nol)
 	utils_dfs_reg_update_nol_ch(dfs->dfs_pdev_obj,
 			(uint8_t *)&chan, 1, DFS_NOL_RESET);
 	utils_dfs_save_nol(dfs->dfs_pdev_obj);
+
+	/*
+	 * Free the NOL element in a thread. This is to avoid freeing the
+	 * timer object from within timer callback function . The nol element
+	 * contains the timer Object.
+	 */
+	qdf_sched_work(NULL, &dfs->dfs_nol_elem_free_work);
 }
 
 void dfs_print_nol(struct wlan_dfs *dfs)
@@ -478,30 +513,32 @@ void dfs_nol_free_list(struct wlan_dfs *dfs)
 void dfs_nol_timer_cleanup(struct wlan_dfs *dfs)
 {
 	struct dfs_nolelem *nol;
-	os_timer_t *nol_timer_list;
-	uint8_t i;
-
-	nol_timer_list = (os_timer_t *)qdf_mem_malloc(
-			sizeof(os_timer_t) * dfs->dfs_nol_count);
-
-	if (!nol_timer_list) {
-		dfs_alert(dfs, WLAN_DEBUG_DFS_ALWAYS, "failed to allocate NOL timer memory!");
-		return;
-	}
 
 	WLAN_DFSNOL_LOCK(dfs);
 	nol = dfs->dfs_nol;
-	for (i = 0; (nol != NULL) && (i < dfs->dfs_nol_count);
-			i++, nol = nol->nol_next)
-		nol_timer_list[i] = nol->nol_timer;
-	WLAN_DFSNOL_UNLOCK(dfs);
+	while (nol) {
+		dfs->dfs_nol = nol->nol_next;
+		dfs->dfs_nol_count--;
 
-	for (i = 0; i < dfs->dfs_nol_count; i++)
-		qdf_timer_sync_cancel(&nol_timer_list[i]);
+		if (!qdf_timer_stop(&nol->nol_timer)) {
+			/*
+			 * Unlock is required so that when we sync with the
+			 * nol_timeout timer we do not run into deadlock.
+			 */
+			WLAN_DFSNOL_UNLOCK(dfs);
+			qdf_timer_sync_cancel(&(nol->nol_timer));
+			WLAN_DFSNOL_LOCK(dfs);
+		}
 
-	DFS_NOL_FREE_LIST_LOCKED(dfs);
+		qdf_mem_free(nol);
+		nol = dfs->dfs_nol;
+	}
+	WLAN_DFSNOL_UNLOCK(dfs);
+}
 
-	qdf_mem_free(nol_timer_list);
+void dfs_nol_workqueue_cleanup(struct wlan_dfs *dfs)
+{
+	qdf_flush_work(&dfs->dfs_nol_elem_free_work);
 }
 
 int dfs_get_use_nol(struct wlan_dfs *dfs)

+ 3 - 2
umac/dfs/core/src/misc/dfs_process_radar_found_ind.c

@@ -404,8 +404,9 @@ QDF_STATUS dfs_process_radar_ind(struct wlan_dfs *dfs,
 	 * from precac-required-list and precac-done-list to
 	 * precac-nol-list.
 	 */
-	dfs_mark_precac_dfs(dfs,
-			dfs->is_radar_found_on_secondary_seg);
+	if (dfs->dfs_precac_enable)
+		dfs_mark_precac_dfs(dfs,
+				dfs->is_radar_found_on_secondary_seg);
 
 	if (!dfs->dfs_is_offload_enabled) {
 		if (dfs->is_radar_found_on_secondary_seg) {