Przeglądaj źródła

qcacmn: Add changes to add scan entry in scan cache

Add changes to add scan entry in scan cache.
Also takes care of duplicate detection and update.

Change-Id: I6715e75bdfc7e703a644b165e699662fa05f4c62
CRs-Fixed: 1095299
Abhishek Singh 8 lat temu
rodzic
commit
d4e600f7d6

+ 451 - 0
umac/scan/core/src/wlan_scan_cache_db.c

@@ -18,6 +18,16 @@
 
 /*
  * DOC: contains scan cache api and functionality
+ * The Scan entries are protected by scan_db_lock. Holding the lock
+ * for whole scan operation during get/flush scan results may take
+ * more than 5 ms and thus ref count is used along with scan_db_lock.
+ * Below are the operation on scan cache entry:
+ * - While adding new node to the entry scan_db_lock is taken and ref_cnt
+ *   is initialized and incremented.
+ * - While reading the entry ref_cnt is incremented while holding the lock.
+ * - Once reading operation is done ref_cnt is decremented while holding
+ *   the lock.
+ * - Once ref_cnt become 0 the node is deleted from the scan cache.
  */
 #include <qdf_status.h>
 #include <wlan_objmgr_psoc_obj.h>
@@ -28,6 +38,440 @@
 #include "wlan_scan_main.h"
 #include "wlan_scan_cache_db_i.h"
 
+/**
+ * scm_del_scan_node() - API to remove scan node from the list
+ * @list: hash list
+ * @scan_node: node to be removed
+ *
+ * This should be called while holding scan_db_lock.
+ *
+ * Return: void
+ */
+static void scm_del_scan_node(qdf_list_t *list,
+	struct scan_cache_node *scan_node)
+{
+	QDF_STATUS status;
+
+	status = qdf_list_remove_node(list, &scan_node->node);
+	if (QDF_IS_STATUS_SUCCESS(status)) {
+		util_scan_free_cache_entry(scan_node->entry);
+		qdf_mem_free(scan_node);
+	}
+}
+
+/**
+ * scm_del_scan_node_from_db() - API to del the scan entry
+ * @scan_db: scan database
+ * @scan_entry:entry scan_node
+ *
+ * API to flush the scan entry. This should be called while
+ * holding scan_db_lock.
+ *
+ * Return: QDF status.
+ */
+static QDF_STATUS scm_del_scan_node_from_db(struct scan_dbs *scan_db,
+	struct scan_cache_node *scan_node)
+{
+	QDF_STATUS status = QDF_STATUS_SUCCESS;
+	uint8_t hash_idx;
+
+	if (!scan_node)
+		return QDF_STATUS_E_INVAL;
+
+	hash_idx = SCAN_GET_HASH(scan_node->entry->bssid.bytes);
+	scm_del_scan_node(&scan_db->scan_hash_tbl[hash_idx], scan_node);
+	scan_db->num_entries--;
+
+	return status;
+}
+
+/**
+ * scm_scan_entry_get_ref() - api to increase ref count of scan entry
+ * @scan_node: scan node
+ *
+ * Return: void
+ */
+static void scm_scan_entry_get_ref(struct scan_cache_node *scan_node)
+{
+	if (scan_node == NULL) {
+		scm_err("scan_node is NULL");
+		QDF_ASSERT(0);
+		return;
+	}
+	qdf_atomic_inc(&scan_node->ref_cnt);
+}
+
+/**
+ * scm_scan_entry_put_ref() - Api to decrease ref count of scan entry
+ * and free if it become 0
+ * @scan_db: scan database
+ * @scan_node: scan node
+ * @lock_needed: if scan_db_lock is needed
+ *
+ * Return: void
+ */
+static void scm_scan_entry_put_ref(struct scan_dbs *scan_db,
+	struct scan_cache_node *scan_node, bool lock_needed)
+{
+
+	if (!scan_node) {
+		scm_err("scan_node is NULL");
+		QDF_ASSERT(0);
+		return;
+	}
+
+	if (!qdf_atomic_read(&scan_node->ref_cnt)) {
+		scm_err("scan_node ref cnt is 0");
+		QDF_ASSERT(0);
+		return;
+	}
+
+	if (lock_needed)
+		qdf_spin_lock_bh(&scan_db->scan_db_lock);
+
+	/* Decrement ref count, free scan_node, if ref count == 0 */
+	if (qdf_atomic_dec_and_test(&scan_node->ref_cnt))
+		scm_del_scan_node_from_db(scan_db, scan_node);
+
+	if (lock_needed)
+		qdf_spin_unlock_bh(&scan_db->scan_db_lock);
+}
+
+/**
+ * scm_add_scan_node() - API to add scan node
+ * @scan_db: data base
+ * @scan_node: node to be removed
+ *
+ * Return: void
+ */
+static void scm_add_scan_node(struct scan_dbs *scan_db,
+	struct scan_cache_node *scan_node)
+{
+	uint8_t hash_idx;
+
+	hash_idx =
+		SCAN_GET_HASH(scan_node->entry->bssid.bytes);
+
+	qdf_spin_lock_bh(&scan_db->scan_db_lock);
+	qdf_atomic_init(&scan_node->ref_cnt);
+	scm_scan_entry_get_ref(scan_node);
+	qdf_list_insert_back(&scan_db->scan_hash_tbl[hash_idx],
+			&scan_node->node);
+	scan_db->num_entries++;
+	qdf_spin_unlock_bh(&scan_db->scan_db_lock);
+}
+
+
+/**
+ * scm_get_next_node() - API get the next scan node from
+ * the list
+ * @list: hash list
+ * @scan_node: node to be removed
+ *
+ * API get the next node from the list. If cur_node is NULL
+ * it will return first node of the list
+ *
+ * Return: next scan cache node
+ */
+static struct scan_cache_node *
+scm_get_next_node(struct scan_dbs *scan_db,
+	qdf_list_t *list,
+	struct scan_cache_node *cur_node)
+{
+	struct scan_cache_node *next_node = NULL;
+	qdf_list_node_t *next_list = NULL;
+
+	qdf_spin_lock_bh(&scan_db->scan_db_lock);
+	if (cur_node) {
+		qdf_list_peek_next(list,
+			&cur_node->node, &next_list);
+		/* Decrement the ref count of the previous node */
+		scm_scan_entry_put_ref(scan_db,
+			cur_node, false);
+	} else {
+		qdf_list_peek_front(list, &next_list);
+	}
+	/* Increase the ref count of the obtained node */
+	if (next_list) {
+		next_node = qdf_container_of(next_list,
+			struct scan_cache_node, node);
+		scm_scan_entry_get_ref(next_node);
+	}
+	qdf_spin_unlock_bh(&scan_db->scan_db_lock);
+
+	return next_node;
+}
+
+/**
+ * scm_check_and_age_out() - check and age out the old entries
+ * @scan_db: scan db
+ * @scan_node: node to check for age out
+ *
+ * Return: void
+ */
+static void scm_check_and_age_out(struct scan_dbs *scan_db,
+	struct scan_cache_node *node)
+{
+	if (util_scan_entry_age(node->entry) >=
+	   SCAN_CACHE_AGING_TIME)
+		scm_scan_entry_put_ref(scan_db, node, true);
+}
+
+void scm_age_out_entries(struct scan_dbs *scan_db)
+{
+	int i;
+	struct scan_cache_node *cur_node = NULL;
+	struct scan_cache_node *next_node = NULL;
+
+	for (i = 0 ; i < SCAN_HASH_SIZE; i++) {
+		cur_node = scm_get_next_node(scan_db,
+			&scan_db->scan_hash_tbl[i], NULL);
+		while (cur_node) {
+			scm_check_and_age_out(scan_db, cur_node);
+			next_node = scm_get_next_node(scan_db,
+				&scan_db->scan_hash_tbl[i], cur_node);
+			cur_node = next_node;
+			next_node = NULL;
+		}
+	}
+}
+
+/**
+ * scm_flush_oldest_entry() - flust out the oldest entry
+ * @scan_db: scan db from which oldest etry needs to be flushed
+ *
+ * Return: QDF_STATUS
+ */
+static QDF_STATUS scm_flush_oldest_entry(struct scan_dbs *scan_db)
+{
+	int i;
+	struct scan_cache_node *oldest_node = NULL;
+	struct scan_cache_node *cur_node;
+	qdf_list_node_t *cur_list = NULL;
+
+	qdf_spin_lock_bh(&scan_db->scan_db_lock);
+	for (i = 0 ; i < SCAN_HASH_SIZE; i++) {
+		cur_node = NULL;
+		qdf_list_peek_front(&scan_db->scan_hash_tbl[i],
+				&cur_list);
+		/*
+		 * Check only the first node if present as new
+		 * entry are added to tail and thus first
+		 * node is the oldest
+		 */
+		if (cur_list) {
+			cur_node = qdf_container_of(cur_list,
+				struct scan_cache_node, node);
+			if (!oldest_node ||
+			   (util_scan_entry_age(oldest_node->entry) <
+			   util_scan_entry_age(cur_node->entry)))
+				oldest_node = cur_node;
+		}
+	}
+	scm_scan_entry_put_ref(scan_db, oldest_node, false);
+	qdf_spin_unlock_bh(&scan_db->scan_db_lock);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * scm_update_alt_wcn_ie() - update the alternate WCN IE
+ * @from: copy from
+ * @dst: copy to
+ *
+ * Return: void
+ */
+static void scm_update_alt_wcn_ie(struct scan_cache_entry *from,
+	struct scan_cache_entry *dst)
+{
+	uint32_t alt_wcn_ie_len;
+
+	if (from->frm_subtype == dst->frm_subtype)
+		return;
+
+	if (!from->ie_list.wcn && !dst->ie_list.wcn)
+		return;
+
+	/* Existing WCN IE is empty. */
+	if (!from->ie_list.wcn)
+		return;
+
+	alt_wcn_ie_len = 2 + from->ie_list.wcn[1];
+	if (alt_wcn_ie_len > WLAN_MAX_IE_LEN + 2) {
+		scm_err("invalid IE len");
+		return;
+	}
+
+	if (!dst->alt_wcn_ie.ptr) {
+		/* allocate this additional buffer for alternate WCN IE */
+		dst->alt_wcn_ie.ptr = qdf_mem_malloc(WLAN_MAX_IE_LEN + 2);
+		if (!dst->alt_wcn_ie.ptr) {
+			scm_err("failed to allocate memory");
+			return;
+		}
+	}
+	qdf_mem_copy(dst->alt_wcn_ie.ptr,
+		from->ie_list.wcn, alt_wcn_ie_len);
+	dst->alt_wcn_ie.len = alt_wcn_ie_len;
+}
+
+/**
+ * scm_add_scan_entry() - add new scan entry to the database
+ * @scan_db: scan database
+ * @scan_params: new entry to be added
+ *
+ * Return: QDF_STATUS
+ */
+static QDF_STATUS scm_add_scan_entry(struct scan_dbs *scan_db,
+	struct scan_cache_entry *scan_params)
+{
+	struct scan_cache_node *scan_node;
+	QDF_STATUS status;
+
+	if (scan_db->num_entries >= MAX_SCAN_CACHE_SIZE) {
+		status = scm_flush_oldest_entry(scan_db);
+		if (QDF_IS_STATUS_ERROR(status))
+			return status;
+	}
+
+	scan_node = qdf_mem_malloc(sizeof(*scan_node));
+	if (!scan_node)
+		return QDF_STATUS_E_NOMEM;
+
+	scan_node->entry = scan_params;
+	scm_add_scan_node(scan_db, scan_node);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * scm_delete_duplicate_entry() - remove duplicate node entry
+ * @scan_db: scan database
+ * @scan_params: new entry to be added
+ * @scan_node: old entry to removed
+ *
+ * Remove duplicate node after copying required
+ * info into new entry
+ *
+ * Return: void
+ */
+static void scm_delete_duplicate_entry(struct scan_dbs *scan_db,
+	struct scan_cache_entry *scan_params,
+	struct scan_cache_node *scan_node)
+{
+	struct scan_cache_entry *scan_entry;
+
+	scan_entry = scan_node->entry;
+	/* If old entry have the ssid but new entry does not */
+	if (!scan_params->ssid.length && scan_entry->ssid.length) {
+		uint64_t time_gap;
+
+		/*
+		 * New entry has a hidden SSID and old one has the SSID.
+		 * Add the entry by using the ssid of the old entry
+		 * only if diff of saved SSID time and current time is
+		 * less than HIDDEN_SSID_TIME time.
+		 * This will avoid issues in case AP changes its SSID
+		 * while remain hidden.
+		 */
+		time_gap =
+			qdf_mc_timer_get_system_time() -
+			scan_entry->hidden_ssid_timestamp;
+		if (time_gap <= HIDDEN_SSID_TIME) {
+			scan_params->hidden_ssid_timestamp =
+				scan_entry->hidden_ssid_timestamp;
+			scan_params->ssid.length =
+				scan_entry->ssid.length;
+			qdf_mem_copy(scan_params->ssid.ssid,
+				scan_entry->ssid.ssid,
+				scan_params->ssid.length);
+		}
+	}
+	/*
+	 * Use old value for rssi if beacon
+	 * was heard on adjacent channel.
+	 */
+	if (scan_params->channel_mismatch) {
+		scan_params->rssi_raw = scan_entry->rssi_raw;
+		scan_params->rssi_timestamp =
+			scan_entry->rssi_timestamp;
+	}
+	/* copy wsn ie from scan_entry to scan_params*/
+	scm_update_alt_wcn_ie(scan_entry, scan_params);
+
+	/* Mark delete the duplicate node */
+	scm_scan_entry_put_ref(scan_db, scan_node, true);
+}
+
+/**
+ * scm_find_duplicate_and_del() - find duplicate entry if present
+ * and update it
+ * @scan_db: scan db
+ * @entry: input scan cache entry
+ *
+ * Return: true if entry is found and updated else false
+ */
+static bool
+scm_find_duplicate_and_del(struct scan_dbs *scan_db,
+	struct scan_cache_entry *entry)
+{
+	uint8_t hash_idx;
+	struct scan_cache_node *cur_node;
+	struct scan_cache_node *next_node = NULL;
+
+	hash_idx = SCAN_GET_HASH(entry->bssid.bytes);
+
+	cur_node = scm_get_next_node(scan_db,
+		   &scan_db->scan_hash_tbl[hash_idx], NULL);
+
+	while (cur_node) {
+		if (util_is_scan_entry_match(entry,
+		   cur_node->entry)) {
+			scm_delete_duplicate_entry(scan_db,
+				entry, cur_node);
+			scm_scan_entry_put_ref(scan_db,
+				cur_node, true);
+			return true;
+		}
+		next_node = scm_get_next_node(scan_db,
+			 &scan_db->scan_hash_tbl[hash_idx], cur_node);
+		cur_node = next_node;
+		next_node = NULL;
+	}
+
+	return false;
+}
+
+/**
+ * scm_add_update_entry() - add or update scan entry
+ * @scan_db: scan database
+ * @scan_params: new received entry
+ *
+ * Return: QDF_STATUS
+ */
+static QDF_STATUS scm_add_update_entry(struct scan_dbs *scan_db,
+	struct scan_cache_entry *scan_params)
+{
+	QDF_STATUS status;
+
+	/* SSID shouldn't be NULL in probe resp */
+	if (scan_params->frm_subtype ==
+	   MGMT_SUBTYPE_PROBE_RESP &&
+	   !scan_params->ie_list.ssid)
+		return QDF_STATUS_E_INVAL;
+
+	/* CSA or ECSA present ignore */
+	if (scan_params->ie_list.csa ||
+	   scan_params->ie_list.xcsa ||
+	   scan_params->ie_list.cswrp)
+		return QDF_STATUS_E_INVAL;
+
+	scm_find_duplicate_and_del(scan_db, scan_params);
+	status = scm_add_scan_entry(scan_db, scan_params);
+
+	return status;
+}
+
 QDF_STATUS scm_handle_bcn_probe(struct scheduler_msg *msg)
 {
 	struct scan_bcn_probe_event *bcn;
@@ -106,6 +550,13 @@ QDF_STATUS scm_handle_bcn_probe(struct scheduler_msg *msg)
 	if (scan_obj->cb.inform_beacon)
 		scan_obj->cb.inform_beacon(pdev, scan_entry);
 
+	status = scm_add_update_entry(scan_db, scan_entry);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		util_scan_free_cache_entry(scan_entry);
+		scm_err("failed to add entry");
+		goto free_nbuf;
+	}
+
 free_nbuf:
 	if (pdev)
 		wlan_objmgr_pdev_release_ref(pdev, WLAN_SCAN_ID);

+ 40 - 0
umac/scan/dispatcher/src/wlan_scan_utils_api.c

@@ -113,6 +113,46 @@ static enum wlan_band scm_chan_to_band(uint32_t chan)
 	return WLAN_BAND_5_GHZ;
 }
 
+bool util_is_scan_entry_match(
+	struct scan_cache_entry *entry1,
+	struct scan_cache_entry *entry2)
+{
+
+	if (entry1->cap_info.wlan_caps.ess !=
+	   entry1->cap_info.wlan_caps.ess)
+		return false;
+
+	if (entry1->cap_info.wlan_caps.ess &&
+	   !qdf_mem_cmp(entry1->bssid.bytes,
+	   entry1->bssid.bytes, QDF_MAC_ADDR_SIZE) &&
+	   scm_chan_to_band(
+	   entry1->channel.chan_idx) ==
+	   scm_chan_to_band(entry2->channel.chan_idx)) {
+		/* Check for BSS */
+		if (util_is_ssid_match(
+		   &entry1->ssid, &entry2->ssid))
+			return true;
+	} else if (entry1->cap_info.wlan_caps.ibss &&
+	   (entry1->channel.chan_idx ==
+	   entry2->channel.chan_idx)) {
+		/*
+		 * Same channel cannot have same SSID for
+		 * different IBSS, so no need to check BSSID
+		 */
+		if (util_is_ssid_match(
+		   &entry1->ssid, &entry2->ssid))
+			return true;
+	} else if (!entry1->cap_info.wlan_caps.ibss &&
+	   !entry1->cap_info.wlan_caps.ess &&
+	   !qdf_mem_cmp(entry1->bssid.bytes,
+	   entry1->bssid.bytes, QDF_MAC_ADDR_SIZE)) {
+		/* In case of P2P devices, ess and ibss will be set to zero */
+		return true;
+	}
+
+	return false;
+}
+
 static bool util_is_pureg_rate(uint8_t *rates, uint8_t nrates)
 {
 	static const uint8_t g_rates[] = {12, 18, 24, 36, 48, 72, 96, 108};