Browse Source

qcacld-3.0: Add high priority work to update netdev features

In certain special scenarios, we need to update the
netdev features of a particular adapter eg: enabling or
disabling tso or hardware checksum incase of legacy clients.

Such an update of the netdev features is to be done only
after the connection, since the connection mode plays an
important role in identifying the features which are to be
updated.

The netdev features can be updated only while holding the
rtnl lock. Also taking into account that the protocol layer,
where we get the idea of the mode of connection, is under its
own locks, updating the netdev features while holding rtnl lock
can often lead to deadlock.

Hence to mitigate the above mentioned deadlock issue, we always
queue a high-priority work to update the netdev features for
every adapter. The need for high-priority work arises from the need
to maintain the roaming KPIs.

Change-Id: Ie9218b08931dea099c6194efa7434d888b849e8d
CRs-Fixed: 2735636
Rakesh Pillai 4 years ago
parent
commit
64b80de92f
3 changed files with 189 additions and 9 deletions
  1. 41 0
      core/hdd/inc/wlan_hdd_main.h
  2. 29 9
      core/hdd/src/wlan_hdd_assoc.c
  3. 119 0
      core/hdd/src/wlan_hdd_main.c

+ 41 - 0
core/hdd/inc/wlan_hdd_main.h

@@ -1118,6 +1118,8 @@ struct hdd_context;
  * @gro_flushed: Flag to indicate if GRO explicit flush is done or not
  * @handle_feature_update: Handle feature update only if it is triggered
  *			   by hdd_netdev_feature_update
+ * @netdev_features_update_work: work for handling the netdev features update
+				 for the adapter.
  */
 struct hdd_adapter {
 	/* Magic cookie for adapter sanity verification.  Note that this
@@ -1415,6 +1417,8 @@ struct hdd_adapter {
 	uint8_t gro_disallowed[DP_MAX_RX_THREADS];
 	uint8_t gro_flushed[DP_MAX_RX_THREADS];
 	bool handle_feature_update;
+
+	qdf_work_t netdev_features_update_work;
 };
 
 #define WLAN_HDD_GET_STATION_CTX_PTR(adapter) (&(adapter)->session.station)
@@ -1690,6 +1694,7 @@ struct hdd_fw_ver_info {
  * @current_pcie_gen_speed: current pcie gen speed
  * @pm_qos_req: pm_qos request for all cpu cores
  * @qos_cpu_mask: voted cpu core mask
+ * @adapter_ops_wq: High priority workqueue for handling adapter operations
  */
 struct hdd_context {
 	struct wlan_objmgr_psoc *psoc;
@@ -2021,6 +2026,7 @@ struct hdd_context {
 		uint8_t gro_force_flush[DP_MAX_RX_THREADS];
 	} dp_agg_param;
 	int current_pcie_gen_speed;
+	qdf_workqueue_t *adapter_ops_wq;
 };
 
 /**
@@ -2814,6 +2820,41 @@ hdd_is_low_tput_gro_enable(struct hdd_context *hdd_ctx)
 
 #endif /*WLAN_FEATURE_DP_BUS_BANDWIDTH*/
 
+/**
+ * hdd_init_adapter_ops_wq() - Init global workqueue for adapter operations.
+ * @hdd_ctx: pointer to HDD context
+ *
+ * Return: QDF_STATUS_SUCCESS if workqueue is allocated,
+ *	   QDF_STATUS_E_NOMEM if workqueue aloocation fails.
+ */
+QDF_STATUS hdd_init_adapter_ops_wq(struct hdd_context *hdd_ctx);
+
+/**
+ * hdd_deinit_adapter_ops_wq() - Deinit global workqueue for adapter operations.
+ * @hdd_ctx: pointer to HDD context
+ *
+ * Return: None
+ */
+void hdd_deinit_adapter_ops_wq(struct hdd_context *hdd_ctx);
+
+/**
+ * hdd_adapter_feature_update_work_init() - Init per adapter work for netdev
+ *					    feature update
+ * @adapter: pointer to adapter structure
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS hdd_adapter_feature_update_work_init(struct hdd_adapter *adapter);
+
+/**
+ * hdd_adapter_feature_update_work_deinit() - Deinit per adapter work for
+ *					      netdev feature update
+ * @adapter: pointer to adapter structure
+ *
+ * Return: QDF_STATUS
+ */
+void hdd_adapter_feature_update_work_deinit(struct hdd_adapter *adapter);
+
 int hdd_qdf_trace_enable(QDF_MODULE_ID module_id, uint32_t bitmask);
 
 int hdd_init(void);

+ 29 - 9
core/hdd/src/wlan_hdd_assoc.c

@@ -1599,8 +1599,6 @@ static void hdd_send_association_event(struct net_device *dev,
 
 		if (ucfg_pkt_capture_get_pktcap_mode(hdd_ctx->psoc))
 			ucfg_pkt_capture_record_channel(adapter->vdev);
-
-		hdd_netdev_update_features(adapter);
 	} else {                /* Not Associated */
 		hdd_nofl_info("%s(vdevid-%d): disconnected", dev->name,
 			      adapter->vdev_id);
@@ -2734,6 +2732,33 @@ void hdd_clear_fils_connection_info(struct hdd_adapter *adapter)
 }
 #endif
 
+/**
+ * hdd_netif_queue_enable() - Enable the network queue for a
+ *			      particular adapter.
+ * @adapter: pointer to the adapter structure
+ *
+ * This function schedules a work to update the netdev features
+ * and enable the network queue if the feature "disable checksum/tso
+ * for legacy connections" is enabled via INI. If not, it will
+ * retain the existing behavior by just enabling the network queues.
+ *
+ * Returns: none
+ */
+static inline void hdd_netif_queue_enable(struct hdd_adapter *adapter)
+{
+	ol_txrx_soc_handle soc = cds_get_context(QDF_MODULE_ID_SOC);
+	struct hdd_context *hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+
+	if (cdp_cfg_get(soc, cfg_dp_disable_legacy_mode_csum_offload)) {
+		qdf_queue_work(0, hdd_ctx->adapter_ops_wq,
+			       &adapter->netdev_features_update_work);
+	} else {
+		wlan_hdd_netif_queue_control(adapter,
+					     WLAN_WAKE_ALL_NETIF_QUEUE,
+					     WLAN_CONTROL_PATH);
+	}
+}
+
 /**
  * hdd_association_completion_handler() - association completion handler
  * @adapter: pointer to adapter
@@ -3222,10 +3247,7 @@ hdd_association_completion_handler(struct hdd_adapter *adapter,
 						roam_info,
 						roam_info->bss_desc);
 				hdd_debug("Enabling queues");
-				wlan_hdd_netif_queue_control(adapter,
-						WLAN_WAKE_ALL_NETIF_QUEUE,
-						WLAN_CONTROL_PATH);
-
+				hdd_netif_queue_enable(adapter);
 			}
 			qdf_mem_free(rsp_rsn_ie);
 		} else {
@@ -3279,9 +3301,7 @@ hdd_association_completion_handler(struct hdd_adapter *adapter,
 
 			/* Start the tx queues */
 			hdd_debug("Enabling queues");
-			wlan_hdd_netif_queue_control(adapter,
-						   WLAN_WAKE_ALL_NETIF_QUEUE,
-						   WLAN_CONTROL_PATH);
+			hdd_netif_queue_enable(adapter);
 		}
 		qdf_mem_free(reqRsnIe);
 

+ 119 - 0
core/hdd/src/wlan_hdd_main.c

@@ -3775,6 +3775,7 @@ int hdd_wlan_start_modules(struct hdd_context *hdd_ctx, bool reinit)
 		}
 
 		hdd_bus_bandwidth_init(hdd_ctx);
+		hdd_init_adapter_ops_wq(hdd_ctx);
 		pld_set_fw_log_mode(hdd_ctx->parent_dev,
 				    hdd_ctx->config->enable_fw_log);
 		ret = hdd_hif_open(qdf_dev->dev, qdf_dev->drv_hdl, qdf_dev->bid,
@@ -4005,6 +4006,7 @@ hif_close:
 	hif_ctx = cds_get_context(QDF_MODULE_ID_HIF);
 	hdd_hif_close(hdd_ctx, hif_ctx);
 power_down:
+	hdd_deinit_adapter_ops_wq(hdd_ctx);
 	hdd_bus_bandwidth_deinit(hdd_ctx);
 	if (!reinit && !unint)
 		pld_power_off(qdf_dev->dev);
@@ -5051,6 +5053,10 @@ hdd_alloc_station_adapter(struct hdd_context *hdd_ctx, tSirMacAddr mac_addr,
 		goto free_net_dev;
 	}
 
+	qdf_status = hdd_adapter_feature_update_work_init(adapter);
+	if (QDF_IS_STATUS_ERROR(qdf_status))
+		goto free_net_dev;
+
 	init_completion(&adapter->vdev_destroy_event);
 
 	adapter->offloads_configured = false;
@@ -5831,6 +5837,7 @@ static void hdd_cleanup_adapter(struct hdd_context *hdd_ctx,
 	wlan_hdd_debugfs_csr_deinit(adapter);
 
 	hdd_debugfs_exit(adapter);
+	hdd_adapter_feature_update_work_deinit(adapter);
 
 	/*
 	 * The adapter is marked as closed. When hdd_wlan_exit() call returns,
@@ -10029,6 +10036,117 @@ void hdd_bus_bandwidth_deinit(struct hdd_context *hdd_ctx)
 }
 #endif /*WLAN_FEATURE_DP_BUS_BANDWIDTH*/
 
+/**
+ * __hdd_adapter_param_update_work() - Gist of the work to process
+ *				       netdev feature update.
+ * @adapter: pointer to adapter structure
+ *
+ * This function assumes that the adapter pointer is always valid.
+ * So the caller shoudl always validate adapter pointer before calling
+ * this function
+ *
+ * Returns: None
+ */
+static inline void
+__hdd_adapter_param_update_work(struct hdd_adapter *adapter)
+{
+	/**
+	 * This check is needed in case the work got scheduled after the
+	 * interface got disconnected. During disconnection, the network queues
+	 * are paused and hence should not be, mistakenly, restarted here.
+	 * There are two approaches to handle this case
+	 * 1) Flush the work during disconnection
+	 * 2) Check for connected state in work
+	 *
+	 * Since the flushing of work during disconnection will need to be
+	 * done at multiple places or entry points, instead its preferred to
+	 * check the connection state and skip the operation here.
+	 */
+	if (!hdd_adapter_is_connected_sta(adapter))
+		return;
+
+	hdd_netdev_update_features(adapter);
+
+	hdd_debug("Enabling queues");
+	wlan_hdd_netif_queue_control(adapter, WLAN_WAKE_ALL_NETIF_QUEUE,
+				     WLAN_CONTROL_PATH);
+}
+
+/**
+ * hdd_adapter_param_update_work() - work to process the netdev features
+ *				     update.
+ * @arg: private data passed to work
+ *
+ * Returns: None
+ */
+static void hdd_adapter_param_update_work(void *arg)
+{
+	struct hdd_adapter *adapter = arg;
+	struct osif_vdev_sync *vdev_sync;
+	int errno;
+
+	if (hdd_validate_adapter(adapter)) {
+		hdd_err("netdev features update request for invalid adapter");
+		return;
+	}
+
+	errno = osif_vdev_sync_op_start(adapter->dev, &vdev_sync);
+	if (errno)
+		return;
+
+	__hdd_adapter_param_update_work(adapter);
+
+	osif_vdev_sync_op_stop(vdev_sync);
+}
+
+QDF_STATUS hdd_init_adapter_ops_wq(struct hdd_context *hdd_ctx)
+{
+	hdd_enter();
+
+	hdd_ctx->adapter_ops_wq =
+		qdf_alloc_high_prior_ordered_workqueue("hdd_adapter_ops_wq");
+	if (!hdd_ctx->adapter_ops_wq)
+		return QDF_STATUS_E_NOMEM;
+
+	hdd_exit();
+
+	return QDF_STATUS_SUCCESS;
+}
+
+void hdd_deinit_adapter_ops_wq(struct hdd_context *hdd_ctx)
+{
+	hdd_enter();
+
+	qdf_flush_workqueue(0, hdd_ctx->adapter_ops_wq);
+	qdf_destroy_workqueue(0, hdd_ctx->adapter_ops_wq);
+
+	hdd_exit();
+}
+
+QDF_STATUS hdd_adapter_feature_update_work_init(struct hdd_adapter *adapter)
+{
+	QDF_STATUS status;
+
+	hdd_enter();
+
+	status = qdf_create_work(0, &adapter->netdev_features_update_work,
+				 hdd_adapter_param_update_work, adapter);
+
+	hdd_exit();
+
+	return status;
+}
+
+void hdd_adapter_feature_update_work_deinit(struct hdd_adapter *adapter)
+{
+	hdd_enter();
+
+	qdf_cancel_work(&adapter->netdev_features_update_work);
+	qdf_flush_work(&adapter->netdev_features_update_work);
+
+	hdd_exit();
+}
+
 static uint8_t *convert_level_to_string(uint32_t level)
 {
 	switch (level) {
@@ -13805,6 +13923,7 @@ int hdd_wlan_stop_modules(struct hdd_context *hdd_ctx, bool ftm_mode)
 	hdd_sap_destroy_ctx_all(hdd_ctx, is_recovery_stop);
 	hdd_sta_destroy_ctx_all(hdd_ctx);
 	pld_request_bus_bandwidth(hdd_ctx->parent_dev, PLD_BUS_WIDTH_NONE);
+	hdd_deinit_adapter_ops_wq(hdd_ctx);
 	hdd_bus_bandwidth_deinit(hdd_ctx);
 	hdd_check_for_leaks(hdd_ctx, is_recovery_stop);
 	hdd_debug_domain_set(QDF_DEBUG_DOMAIN_INIT);