Bladeren bron

qcacld-3.0: Add pending changes for reassoc req

Add pending changes for reassoc req.

Change-Id: I805da0730be9f602fb04135f69c6f339ea8e2ac6
CRs-Fixed: 2958582
Utkarsh Bhatnagar 3 jaren geleden
bovenliggende
commit
790223c7dd

+ 0 - 2
components/mlme/core/src/wlan_mlme_vdev_mgr_interface.c

@@ -1821,8 +1821,6 @@ static struct mlme_ext_ops ext_ops = {
 	.mlme_cm_ext_disconnect_complete_ind_cb = cm_disconnect_complete_ind,
 	.mlme_cm_ext_vdev_down_req_cb = cm_send_vdev_down_req,
 	.mlme_cm_ext_reassoc_req_cb = cm_handle_reassoc_req,
-#ifdef WLAN_FEATURE_HOST_ROAM
 	.mlme_cm_ext_roam_start_ind_cb = cm_handle_roam_start,
 #endif
-#endif
 };

+ 76 - 0
components/umac/mlme/connection_mgr/core/src/wlan_cm_host_roam_preauth.c

@@ -26,6 +26,7 @@
 #include "wlan_cm_roam_api.h"
 #include "wlan_cm_roam_public_struct.h"
 #include "wlan_cm_public_struct.h"
+#include "wlan_mlme_vdev_mgr_interface.h"
 #include "connection_mgr/core/src/wlan_cm_roam.h"
 #include "connection_mgr/core/src/wlan_cm_sm.h"
 #include "connection_mgr/core/src/wlan_cm_main_api.h"
@@ -261,11 +262,86 @@ cm_send_preauth_start_fail(struct cnx_mgr *cm_ctx,
 	return status;
 }
 
+static void cm_flush_invalid_preauth_ap(struct cnx_mgr *cm_ctx,
+					struct cm_roam_req *roam_req)
+{
+	qdf_list_node_t *cur_node = NULL, *next_node = NULL;
+	qdf_list_t *candidate_list;
+	struct scan_cache_node *scan_node = NULL;
+	struct qdf_mac_addr connected_bssid;
+	bool is_valid;
+	uint8_t vdev_id = roam_req->req.vdev_id;
+	struct wlan_objmgr_psoc *psoc;
+	uint8_t enable_mcc_mode = false;
+	qdf_freq_t conc_freq, bss_freq;
+
+	/*
+	 * Only When entering first time (ie cur_candidate is NULL),
+	 * flush invalid APs from the list and if list is not NULL.
+	 */
+	if (roam_req->cur_candidate || !roam_req->candidate_list)
+		return;
+
+	psoc = wlan_vdev_get_psoc(cm_ctx->vdev);
+	if (!psoc)
+		return;
+
+	wlan_mlme_get_mcc_feature(psoc, &enable_mcc_mode);
+
+	wlan_vdev_get_bss_peer_mac(cm_ctx->vdev, &connected_bssid);
+
+	candidate_list = roam_req->candidate_list;
+
+	qdf_list_peek_front(candidate_list, &cur_node);
+
+	while (cur_node) {
+		is_valid = true;
+		qdf_list_peek_next(candidate_list, cur_node, &next_node);
+		scan_node = qdf_container_of(cur_node, struct scan_cache_node,
+					     node);
+		bss_freq = scan_node->entry->channel.chan_freq;
+		if (qdf_is_macaddr_equal(&connected_bssid,
+					 &scan_node->entry->bssid)) {
+			mlme_debug(CM_PREFIX_FMT "Remove connected AP" QDF_MAC_ADDR_FMT " from list",
+				   CM_PREFIX_REF(vdev_id, roam_req->cm_id),
+				   QDF_MAC_ADDR_REF(connected_bssid.bytes));
+			is_valid = false;
+		}
+
+		/*
+		 * Continue if MCC is disabled in INI and if AP
+		 * will create MCC
+		 */
+		if (policy_mgr_concurrent_open_sessions_running(psoc) &&
+		    !enable_mcc_mode) {
+			conc_freq = wlan_get_conc_freq();
+			if (conc_freq && (conc_freq != bss_freq)) {
+				mlme_info(CM_PREFIX_FMT "Remove AP " QDF_MAC_ADDR_FMT ", MCC not supported. freq %d conc_freq %d",
+					  CM_PREFIX_REF(vdev_id, roam_req->cm_id),
+					  QDF_MAC_ADDR_REF(connected_bssid.bytes),
+					  bss_freq, conc_freq);
+				is_valid = false;
+			}
+		}
+
+		if (!is_valid) {
+			qdf_list_remove_node(candidate_list, cur_node);
+			util_scan_free_cache_entry(scan_node->entry);
+			qdf_mem_free(scan_node);
+		}
+
+		cur_node = next_node;
+		next_node = NULL;
+	}
+}
+
 QDF_STATUS cm_host_roam_preauth_start(struct cnx_mgr *cm_ctx,
 				      struct cm_req *cm_req)
 {
 	QDF_STATUS status;
 
+	cm_flush_invalid_preauth_ap(cm_ctx, &cm_req->roam_req);
+
 	status = cm_get_valid_preauth_candidate(&cm_req->roam_req);
 	if (QDF_IS_STATUS_ERROR(status))
 		return status;

+ 129 - 2
components/umac/mlme/connection_mgr/core/src/wlan_cm_host_util.c

@@ -17,12 +17,21 @@
 /**
  * DOC: wlan_cm_host_util.c
  *
- * Implements Host roam (LFR2) utils for connection manager
+ * Implements Host roam (LFR2) reassoc specific legacy code for
+ * connection manager
  */
 
-#include "wlan_cm_roam_api.h"
+#include "wlan_cm_vdev_api.h"
+#include "wlan_scan_api.h"
+#include "wlan_scan_utils_api.h"
+#include "wlan_policy_mgr_api.h"
+#include "wlan_roam_debug.h"
+#include "wni_api.h"
+#include "wlan_logging_sock_svc.h"
 #include "connection_mgr/core/src/wlan_cm_roam.h"
 
+#define ROAM_AP_AGE_LIMIT_MS                     10000
+
 /*
  * cm_copy_ssids_from_rso_config_params() - copy SSID from rso_config_params
  * to scan filter
@@ -118,5 +127,123 @@ QDF_STATUS cm_update_advance_roam_scan_filter(
 	else if (rso_cfg->rsn_cap & WLAN_CRYPTO_RSN_CAP_MFP_ENABLED)
 		filter->pmf_cap = WLAN_PMF_CAPABLE;
 
+	/* Dont Consider AP older than ROAM_AP_AGE_LIMIT_MS */
+	filter->age_threshold = ROAM_AP_AGE_LIMIT_MS;
+
 	return QDF_STATUS_SUCCESS;
 }
+
+QDF_STATUS
+cm_handle_reassoc_req(struct wlan_objmgr_vdev *vdev,
+		      struct wlan_cm_vdev_reassoc_req *req)
+{
+	struct cm_vdev_join_req *join_req;
+	struct scheduler_msg msg;
+	QDF_STATUS status;
+	struct wlan_objmgr_pdev *pdev;
+	struct wlan_objmgr_psoc *psoc;
+	struct rso_config *rso_cfg;
+
+	if (!vdev || !req)
+		return QDF_STATUS_E_FAILURE;
+
+	pdev = wlan_vdev_get_pdev(vdev);
+	if (!pdev) {
+		mlme_err(CM_PREFIX_FMT "pdev not found",
+			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
+		return QDF_STATUS_E_INVAL;
+	}
+	psoc = wlan_pdev_get_psoc(pdev);
+	if (!psoc) {
+		mlme_err(CM_PREFIX_FMT "psoc not found",
+			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
+		return QDF_STATUS_E_INVAL;
+	}
+
+	rso_cfg = wlan_cm_get_rso_config(vdev);
+	if (!rso_cfg)
+		return QDF_STATUS_E_NOSUPPORT;
+
+	qdf_mem_zero(&msg, sizeof(msg));
+	join_req = qdf_mem_malloc(sizeof(*join_req));
+	if (!join_req)
+		return QDF_STATUS_E_NOMEM;
+
+	wlan_cm_set_disable_hi_rssi(pdev, req->vdev_id, true);
+	mlme_debug(CM_PREFIX_FMT "Disabling HI_RSSI, AP freq=%d, rssi=%d",
+		   CM_PREFIX_REF(req->vdev_id, req->cm_id),
+		   req->bss->entry->channel.chan_freq,
+		   req->bss->entry->rssi_raw);
+
+	if (rso_cfg->assoc_ie.ptr) {
+		join_req->assoc_ie.ptr = qdf_mem_malloc(rso_cfg->assoc_ie.len);
+		if (!join_req->assoc_ie.ptr)
+			return QDF_STATUS_E_NOMEM;
+		qdf_mem_copy(join_req->assoc_ie.ptr, rso_cfg->assoc_ie.ptr,
+			     rso_cfg->assoc_ie.len);
+		join_req->assoc_ie.len = rso_cfg->assoc_ie.len;
+	}
+
+	join_req->entry = util_scan_copy_cache_entry(req->bss->entry);
+	if (!join_req->entry) {
+		mlme_err(CM_PREFIX_FMT "Failed to copy scan entry",
+			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
+		cm_free_join_req(join_req);
+		return QDF_STATUS_E_NOMEM;
+	}
+	join_req->vdev_id = req->vdev_id;
+	join_req->cm_id = req->cm_id;
+
+	status = cm_csr_handle_join_req(vdev, NULL, join_req, true);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		mlme_err(CM_PREFIX_FMT "fail to fill params from legacy",
+			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
+		cm_free_join_req(join_req);
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	wlan_rec_conn_info(req->vdev_id, DEBUG_CONN_CONNECTING,
+			   req->bss->entry->bssid.bytes,
+			   req->bss->entry->neg_sec_info.key_mgmt,
+			   req->bss->entry->channel.chan_freq);
+
+	/* decrement count for self reassoc */
+	if (req->self_reassoc)
+		policy_mgr_decr_session_set_pcl(psoc,
+						wlan_vdev_mlme_get_opmode(vdev),
+						req->vdev_id);
+	msg.bodyptr = join_req;
+	msg.type = CM_REASSOC_REQ;
+	msg.flush_callback = cm_flush_join_req;
+
+	status = scheduler_post_message(QDF_MODULE_ID_MLME,
+					QDF_MODULE_ID_PE,
+					QDF_MODULE_ID_PE, &msg);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		mlme_err(CM_PREFIX_FMT "msg post fail",
+			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
+		cm_free_join_req(join_req);
+	}
+
+	if (wlan_vdev_mlme_get_opmode(vdev) == QDF_STA_MODE)
+		wlan_register_txrx_packetdump(OL_TXRX_PDEV_ID);
+
+	return status;
+}
+
+QDF_STATUS cm_handle_roam_start(struct wlan_objmgr_vdev *vdev,
+				struct wlan_cm_roam_req *req)
+{
+	if (!vdev || !req) {
+		mlme_err("vdev or req is NULL");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if (req->source == CM_ROAMING_HOST)
+		cm_roam_state_change(wlan_vdev_get_pdev(vdev),
+				     wlan_vdev_get_id(vdev),
+				     WLAN_ROAM_RSO_STOPPED,
+				     REASON_OS_REQUESTED_ROAMING_NOW);
+	return QDF_STATUS_SUCCESS;
+}
+

+ 25 - 0
components/umac/mlme/connection_mgr/core/src/wlan_cm_vdev_api.h

@@ -515,6 +515,14 @@ QDF_STATUS cm_send_vdev_down_req(struct wlan_objmgr_vdev *vdev);
  */
 void cm_free_join_req(struct cm_vdev_join_req *join_req);
 
+/**
+ * cm_flush_join_req() - Process join req flush
+ * @msg: scheduler message
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS cm_flush_join_req(struct scheduler_msg *msg);
+
 /**
  * cm_process_join_req() - Process vdev join req
  * @msg: scheduler message
@@ -558,6 +566,17 @@ QDF_STATUS
 cm_handle_reassoc_req(struct wlan_objmgr_vdev *vdev,
 		      struct wlan_cm_vdev_reassoc_req *req);
 
+/**
+ * cm_handle_roam_start() - roam start indication
+ * @vdev: VDEV object
+ * @req: Connection manager roam request
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS
+cm_handle_roam_start(struct wlan_objmgr_vdev *vdev,
+		     struct wlan_cm_roam_req *req);
+
 /**
  * cm_csr_preauth_done() - Process preauth done from csr part
  * @vdev: vdev object pointer
@@ -582,6 +601,12 @@ cm_handle_reassoc_req(struct wlan_objmgr_vdev *vdev,
 {
 	return QDF_STATUS_SUCCESS;
 }
+static inline QDF_STATUS
+cm_handle_roam_start(struct wlan_objmgr_vdev *vdev,
+		     struct wlan_cm_roam_req *req)
+{
+	return QDF_STATUS_SUCCESS;
+}
 #endif
 
 /**

+ 1 - 101
components/umac/mlme/connection_mgr/core/src/wlan_cm_vdev_connect.c

@@ -1010,7 +1010,7 @@ void cm_free_join_req(struct cm_vdev_join_req *join_req)
 	qdf_mem_free(join_req);
 }
 
-static QDF_STATUS cm_flush_join_req(struct scheduler_msg *msg)
+QDF_STATUS cm_flush_join_req(struct scheduler_msg *msg)
 {
 	struct cm_vdev_join_req *join_req;
 
@@ -1206,106 +1206,6 @@ cm_handle_connect_req(struct wlan_objmgr_vdev *vdev,
 	return status;
 }
 
-#ifdef WLAN_FEATURE_HOST_ROAM
-QDF_STATUS
-cm_handle_reassoc_req(struct wlan_objmgr_vdev *vdev,
-		      struct wlan_cm_vdev_reassoc_req *req)
-{
-	struct cm_vdev_join_req *join_req;
-	struct scheduler_msg msg;
-	QDF_STATUS status;
-	struct wlan_objmgr_pdev *pdev;
-	struct wlan_objmgr_psoc *psoc;
-	struct rso_config *rso_cfg;
-
-	if (!vdev || !req)
-		return QDF_STATUS_E_FAILURE;
-
-	pdev = wlan_vdev_get_pdev(vdev);
-	if (!pdev) {
-		mlme_err(CM_PREFIX_FMT "pdev not found",
-			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
-		return QDF_STATUS_E_INVAL;
-	}
-	psoc = wlan_pdev_get_psoc(pdev);
-	if (!psoc) {
-		mlme_err(CM_PREFIX_FMT "psoc not found",
-			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
-		return QDF_STATUS_E_INVAL;
-	}
-
-	rso_cfg = wlan_cm_get_rso_config(vdev);
-	if (!rso_cfg)
-		return QDF_STATUS_E_NOSUPPORT;
-
-	qdf_mem_zero(&msg, sizeof(msg));
-	join_req = qdf_mem_malloc(sizeof(*join_req));
-	if (!join_req)
-		return QDF_STATUS_E_NOMEM;
-
-	wlan_cm_set_disable_hi_rssi(pdev, req->vdev_id, true);
-	mlme_debug(CM_PREFIX_FMT "Disabling HI_RSSI, AP freq=%d, rssi=%d",
-		   CM_PREFIX_REF(req->vdev_id, req->cm_id),
-		   req->bss->entry->channel.chan_freq,
-		   req->bss->entry->rssi_raw);
-
-	if (rso_cfg->assoc_ie.ptr) {
-		join_req->assoc_ie.ptr = qdf_mem_malloc(rso_cfg->assoc_ie.len);
-		if (!join_req->assoc_ie.ptr)
-			return QDF_STATUS_E_NOMEM;
-		qdf_mem_copy(join_req->assoc_ie.ptr, rso_cfg->assoc_ie.ptr,
-			     rso_cfg->assoc_ie.len);
-		join_req->assoc_ie.len = rso_cfg->assoc_ie.len;
-	}
-
-	join_req->entry = util_scan_copy_cache_entry(req->bss->entry);
-	if (!join_req->entry) {
-		mlme_err(CM_PREFIX_FMT "Failed to copy scan entry",
-			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
-		cm_free_join_req(join_req);
-		return QDF_STATUS_E_NOMEM;
-	}
-	join_req->vdev_id = req->vdev_id;
-	join_req->cm_id = req->cm_id;
-
-	status = cm_csr_handle_join_req(vdev, NULL, join_req, true);
-	if (QDF_IS_STATUS_ERROR(status)) {
-		mlme_err(CM_PREFIX_FMT "fail to fill params from legacy",
-			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
-		cm_free_join_req(join_req);
-		return QDF_STATUS_E_FAILURE;
-	}
-
-	wlan_rec_conn_info(req->vdev_id, DEBUG_CONN_CONNECTING,
-			   req->bss->entry->bssid.bytes,
-			   req->bss->entry->neg_sec_info.key_mgmt,
-			   req->bss->entry->channel.chan_freq);
-
-	/* decrement count for self reassoc */
-	if (req->self_reassoc)
-		policy_mgr_decr_session_set_pcl(psoc,
-						wlan_vdev_mlme_get_opmode(vdev),
-						req->vdev_id);
-	msg.bodyptr = join_req;
-	msg.type = CM_REASSOC_REQ;
-	msg.flush_callback = cm_flush_join_req;
-
-	status = scheduler_post_message(QDF_MODULE_ID_MLME,
-					QDF_MODULE_ID_PE,
-					QDF_MODULE_ID_PE, &msg);
-	if (QDF_IS_STATUS_ERROR(status)) {
-		mlme_err(CM_PREFIX_FMT "msg post fail",
-			 CM_PREFIX_REF(req->vdev_id, req->cm_id));
-		cm_free_join_req(join_req);
-	}
-
-	if (wlan_vdev_mlme_get_opmode(vdev) == QDF_STA_MODE)
-		wlan_register_txrx_packetdump(OL_TXRX_PDEV_ID);
-
-	return status;
-}
-#endif
-
 QDF_STATUS
 cm_send_bss_peer_create_req(struct wlan_objmgr_vdev *vdev,
 			    struct qdf_mac_addr *peer_mac)

+ 0 - 11
components/umac/mlme/connection_mgr/dispatcher/inc/wlan_cm_roam_api.h

@@ -679,17 +679,6 @@ bool cm_is_rsn_or_8021x_sha256_auth_type(struct wlan_objmgr_vdev *vdev);
  */
 QDF_STATUS wlan_cm_host_roam_start(struct scheduler_msg *msg);
 
-/**
- * cm_handle_roam_start() - roam start indication
- * @vdev: VDEV object
- * @req: Connection manager roam request
- *
- * Return: QDF_STATUS
- */
-QDF_STATUS
-cm_handle_roam_start(struct wlan_objmgr_vdev *vdev,
-		     struct wlan_cm_roam_req *req);
-
 /**
  * cm_mlme_roam_preauth_fail() - roam preauth fail
  * @vdev: VDEV object

+ 12 - 26
components/umac/mlme/connection_mgr/dispatcher/src/wlan_cm_roam_api.c

@@ -1221,7 +1221,7 @@ static void cm_rso_chan_to_freq_list(struct wlan_objmgr_pdev *pdev,
 }
 
 #if defined(FEATURE_CM_ENABLE) && defined(WLAN_FEATURE_HOST_ROAM)
-static QDF_STATUS wlan_cm_init_reassoc_timer(struct rso_config *rso_cfg)
+static QDF_STATUS cm_init_reassoc_timer(struct rso_config *rso_cfg)
 {
 	QDF_STATUS status;
 
@@ -1234,7 +1234,7 @@ static QDF_STATUS wlan_cm_init_reassoc_timer(struct rso_config *rso_cfg)
 	return status;
 }
 
-static void wlan_cm_deinit_reassoc_timer(struct rso_config *rso_cfg)
+static void cm_deinit_reassoc_timer(struct rso_config *rso_cfg)
 {
 	/* check if the timer is running */
 	if (QDF_TIMER_STATE_RUNNING ==
@@ -1243,6 +1243,12 @@ static void wlan_cm_deinit_reassoc_timer(struct rso_config *rso_cfg)
 
 	qdf_mc_timer_destroy(&rso_cfg->reassoc_timer);
 }
+#else
+static inline QDF_STATUS cm_init_reassoc_timer(struct rso_config *rso_cfg)
+{
+	return QDF_STATUS_SUCCESS;
+}
+static inline void cm_deinit_reassoc_timer(struct rso_config *rso_cfg) {}
 #endif
 
 QDF_STATUS wlan_cm_rso_config_init(struct wlan_objmgr_vdev *vdev,
@@ -1267,12 +1273,11 @@ QDF_STATUS wlan_cm_rso_config_init(struct wlan_objmgr_vdev *vdev,
 	if (!mlme_obj)
 		return QDF_STATUS_E_INVAL;
 
-#ifdef FEATURE_CM_ENABLE
-#ifdef WLAN_FEATURE_HOST_ROAM
-	status = wlan_cm_init_reassoc_timer(rso_cfg);
+	status = cm_init_reassoc_timer(rso_cfg);
 	if (QDF_IS_STATUS_ERROR(status))
 		return status;
-#endif
+
+#ifdef FEATURE_CM_ENABLE
 	qdf_mutex_create(&rso_cfg->cm_rso_lock);
 #endif
 	cfg_params = &rso_cfg->cfg_param;
@@ -1381,11 +1386,8 @@ void wlan_cm_rso_config_deinit(struct wlan_objmgr_vdev *vdev,
 
 #ifdef FEATURE_CM_ENABLE
 	qdf_mutex_destroy(&rso_cfg->cm_rso_lock);
-#ifdef WLAN_FEATURE_HOST_ROAM
-	wlan_cm_deinit_reassoc_timer(rso_cfg);
-#endif
 #endif
-
+	cm_deinit_reassoc_timer(rso_cfg);
 }
 
 #ifdef FEATURE_CM_ENABLE
@@ -1503,22 +1505,6 @@ QDF_STATUS wlan_cm_host_roam_start(struct scheduler_msg *msg)
 				   CM_ROAMING_FW);
 }
 
-QDF_STATUS cm_handle_roam_start(struct wlan_objmgr_vdev *vdev,
-				struct wlan_cm_roam_req *req)
-{
-	if (!vdev || !req) {
-		mlme_err("vdev or req is NULL");
-		return QDF_STATUS_E_INVAL;
-	}
-
-	if (req->source == CM_ROAMING_HOST)
-		cm_roam_state_change(wlan_vdev_get_pdev(vdev),
-				     wlan_vdev_get_id(vdev),
-				     WLAN_ROAM_RSO_STOPPED,
-				     REASON_OS_REQUESTED_ROAMING_NOW);
-	return QDF_STATUS_SUCCESS;
-}
-
 QDF_STATUS cm_mlme_roam_preauth_fail(struct wlan_objmgr_vdev *vdev,
 				     struct wlan_cm_roam_req *req,
 				     enum wlan_cm_connect_fail_reason reason)

+ 2 - 2
core/mac/inc/sir_api.h

@@ -1032,9 +1032,9 @@ typedef struct sEsePEContext {
 /* Warning Do not add any new param in this struct */
 struct join_req {
 #ifndef FEATURE_CM_ENABLE
-	uint8_t vdev_id;
-	/* (Re) Association Request */
+	uint16_t messageType;   /* eWNI_SME_JOIN_REQ */
 	uint16_t length;
+	uint8_t vdev_id;
 	tSirMacSSid ssId;
 	tAniEdType UCEncryptionType;
 	enum ani_akm_type akm;

+ 1 - 0
core/sme/src/csr/csr_api_roam.c

@@ -13120,6 +13120,7 @@ QDF_STATUS csr_send_join_req_msg(struct mac_context *mac, uint32_t sessionId,
 		if (!QDF_IS_STATUS_SUCCESS(status))
 			break;
 
+		csr_join_req->messageType = messageType;
 		csr_join_req->length = msgLen;
 		csr_join_req->vdev_id = (uint8_t) sessionId;
 		if (pIes->SSID.present &&