/* * Copyright (c) 2017-2019 The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* * 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. Also the cookie will be set to valid value. * - The ref count incremented during adding new node should be decremented only * by a delete operation on the node. But there can be multiple concurrent * delete operations on a node from different threads which may lead to ref * count being decremented multiple time and freeing the node even if node * is in use. So to maintain atomicity between multiple delete operations * on a same node from different threads, a cookie is used to check if node is * logically deleted or not. A delete operation will set the cookie to 0 * making it invalid. So if the 2nd thread find the cookie as invalid it will * not try to delete and decrement the ref count of the node again. * - This Cookie is also used to check if node is valid while iterating through * the scan cache to avoid duplicate entries. * - Once ref_cnt become 0, i.e. it is logically deleted and no thread is using * it the node is physically deleted from the scan cache. * - While reading the node the ref_cnt should be incremented. Once reading * operation is done ref_cnt is decremented. */ #include #include #include #include #include #include #include "wlan_scan_main.h" #include "wlan_scan_cache_db_i.h" #include "wlan_reg_services_api.h" #include "wlan_reg_ucfg_api.h" #include /** * 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 (lock_needed) qdf_spin_lock_bh(&scan_db->scan_db_lock); if (!qdf_atomic_read(&scan_node->ref_cnt)) { if (lock_needed) qdf_spin_unlock_bh(&scan_db->scan_db_lock); scm_err("scan_node ref cnt is 0"); QDF_ASSERT(0); return; } /* 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_scan_entry_del() - API to delete scan node * @scan_db: data base * @scan_node: node to be deleted * * Call must be protected by scan_db->scan_db_lock * * Return: void */ static void scm_scan_entry_del(struct scan_dbs *scan_db, struct scan_cache_node *scan_node) { if (!scan_node) { scm_err("scan node is NULL"); QDF_ASSERT(0); return; } if (scan_node->cookie != SCAN_NODE_ACTIVE_COOKIE) { scm_debug("node is already deleted"); return; } /* Seems node is already deleted */ if (!qdf_atomic_read(&scan_node->ref_cnt)) { scm_debug("node is already deleted ref 0"); return; } scan_node->cookie = 0; scm_scan_entry_put_ref(scan_db, scan_node, false); } /** * scm_add_scan_node() - API to add scan node * @scan_db: data base * @scan_node: node to be added * @dup_node: node before which new node to be added * if it's not NULL, otherwise add node to tail * * Call must be protected by scan_db->scan_db_lock * * Return: void */ static void scm_add_scan_node(struct scan_dbs *scan_db, struct scan_cache_node *scan_node, struct scan_cache_node *dup_node) { uint8_t hash_idx; hash_idx = SCAN_GET_HASH(scan_node->entry->bssid.bytes); qdf_atomic_init(&scan_node->ref_cnt); scan_node->cookie = SCAN_NODE_ACTIVE_COOKIE; scm_scan_entry_get_ref(scan_node); if (!dup_node) qdf_list_insert_back(&scan_db->scan_hash_tbl[hash_idx], &scan_node->node); else qdf_list_insert_before(&scan_db->scan_hash_tbl[hash_idx], &scan_node->node, &dup_node->node); scan_db->num_entries++; } /** * scm_get_next_valid_node() - API get the next valid scan node from * the list * @list: hash list * @cur_node: current node pointer * * API to get next active node from the list. If cur_node is NULL * it will return first node of the list. * Call must be protected by scan_db->scan_db_lock * * Return: next scan node */ static qdf_list_node_t * scm_get_next_valid_node(qdf_list_t *list, qdf_list_node_t *cur_node) { qdf_list_node_t *next_node = NULL; qdf_list_node_t *temp_node = NULL; struct scan_cache_node *scan_node; if (cur_node) qdf_list_peek_next(list, cur_node, &next_node); else qdf_list_peek_front(list, &next_node); while (next_node) { scan_node = qdf_container_of(next_node, struct scan_cache_node, node); if (scan_node->cookie == SCAN_NODE_ACTIVE_COOKIE) return next_node; /* * If node is not valid check for next entry * to get next valid node. */ qdf_list_peek_next(list, next_node, &temp_node); next_node = temp_node; temp_node = NULL; } return next_node; } /** * scm_get_next_node() - API get the next scan node from * the list * @scan_db: scan data base * @list: hash list * @cur_node: current node pointer * * 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) { next_list = scm_get_next_valid_node(list, &cur_node->node); /* Decrement the ref count of the previous node */ scm_scan_entry_put_ref(scan_db, cur_node, false); } else { next_list = scm_get_next_valid_node(list, NULL); } /* 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 * @scan_aging_time: scan cache aging time * * Return: void */ static void scm_check_and_age_out(struct scan_dbs *scan_db, struct scan_cache_node *node, uint32_t scan_aging_time) { if (util_scan_entry_age(node->entry) >= scan_aging_time) { scm_debug("Aging out BSSID: %pM with age %d ms", node->entry->bssid.bytes, util_scan_entry_age(node->entry)); qdf_spin_lock_bh(&scan_db->scan_db_lock); scm_scan_entry_del(scan_db, node); qdf_spin_unlock_bh(&scan_db->scan_db_lock); } } static bool scm_bss_is_connected(struct scan_cache_entry *entry) { if (entry->mlme_info.assoc_state == SCAN_ENTRY_CON_STATE_ASSOC) return true; return false; } void scm_age_out_entries(struct wlan_objmgr_psoc *psoc, struct scan_dbs *scan_db) { int i; struct scan_cache_node *cur_node = NULL; struct scan_cache_node *next_node = NULL; struct scan_default_params *def_param; def_param = wlan_scan_psoc_get_def_params(psoc); if (!def_param) { scm_err("wlan_scan_psoc_get_def_params failed"); return; } 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) { if (!scm_bss_is_connected(cur_node->entry)) scm_check_and_age_out(scan_db, cur_node, def_param->scan_cache_aging_time); 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() - Iterate over scan db and flust out the * oldest entry * @scan_db: scan db from which oldest entry 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; for (i = 0 ; i < SCAN_HASH_SIZE; i++) { /* Get the first valid node for the hash */ cur_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[i], NULL); /* Iterate scan db and flush out oldest node * take ref_cnt for oldest_node */ while (cur_node) { if (!oldest_node || (util_scan_entry_age(oldest_node->entry) < util_scan_entry_age(cur_node->entry))) { if (oldest_node) scm_scan_entry_put_ref(scan_db, oldest_node, true); oldest_node = cur_node; scm_scan_entry_get_ref(oldest_node); } cur_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[i], cur_node); }; } if (oldest_node) { scm_debug("Flush oldest BSSID: %pM with age %d ms", oldest_node->entry->bssid.bytes, util_scan_entry_age(oldest_node->entry)); /* Release ref_cnt taken for oldest_node and delete it */ qdf_spin_lock_bh(&scan_db->scan_db_lock); scm_scan_entry_del(scan_db, oldest_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_atomic(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_update_mlme_info() - update mlme info * @src: source scan entry * @dest: destination scan entry * * Return: void */ static inline void scm_update_mlme_info(struct scan_cache_entry *src, struct scan_cache_entry *dest) { qdf_mem_copy(&dest->mlme_info, &src->mlme_info, sizeof(struct mlme_info)); } /** * scm_copy_info_from_dup_entry() - copy duplicate node info * to new scan entry * @scan_db: scan database * @scan_params: new entry to be added * @scan_node: duplicate entry * * Copy duplicate node info to new entry. * * Return: void */ static void scm_copy_info_from_dup_entry(struct scan_dbs *scan_db, struct scan_cache_entry *scan_params, struct scan_cache_node *scan_node) { struct scan_cache_entry *scan_entry; uint64_t time_gap; scan_entry = scan_node->entry; /* If old entry have the ssid but new entry does not */ if (util_scan_is_null_ssid(&scan_params->ssid) && scan_entry->ssid.length) { /* * 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_entry->ssid.length); } } /* * Due to Rx sensitivity issue, sometime beacons are seen on adjacent * channel so workaround in software is needed. If DS params or HT info * are present driver can get proper channel info from these IEs and set * channel_mismatch so that the older RSSI values are used in new entry. * * For the cases where DS params and HT info is not present, driver * needs to check below conditions to get proper channel and set * channel_mismatch so that the older RSSI values are used in new entry: * -- The old entry channel and new entry channel are not same * -- RSSI is less than -80, this indicate that the signal has leaked * in adjacent channel. */ if ((scan_params->frm_subtype == MGMT_SUBTYPE_BEACON) && !util_scan_entry_htinfo(scan_params) && !util_scan_entry_ds_param(scan_params) && (scan_params->channel.chan_idx != scan_entry->channel.chan_idx) && (scan_params->rssi_raw < ADJACENT_CHANNEL_RSSI_THRESHOLD)) { scan_params->channel.chan_idx = scan_entry->channel.chan_idx; scan_params->channel_mismatch = true; } /* 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->avg_rssi = scan_entry->avg_rssi; scan_params->rssi_timestamp = scan_entry->rssi_timestamp; } else { /* If elapsed time since last rssi update for this * entry is smaller than a thresold, calculate a * running average of the RSSI values. * Otherwise new frames RSSI is more representive * of the signal strength. */ time_gap = scan_params->scan_entry_time - scan_entry->rssi_timestamp; if (time_gap > WLAN_RSSI_AVERAGING_TIME) scan_params->avg_rssi = WLAN_RSSI_IN(scan_params->rssi_raw); else { /* Copy previous average rssi to new entry */ scan_params->avg_rssi = scan_entry->avg_rssi; /* Average with previous samples */ WLAN_RSSI_LPF(scan_params->avg_rssi, scan_params->rssi_raw); } scan_params->rssi_timestamp = scan_params->scan_entry_time; } /* copy wsn ie from scan_entry to scan_params*/ scm_update_alt_wcn_ie(scan_entry, scan_params); /* copy mlme info from scan_entry to scan_params*/ scm_update_mlme_info(scan_entry, scan_params); } /** * scm_find_duplicate() - find duplicate entry, * if present, add input scan entry before it and delete * duplicate entry. otherwise add entry to tail * @scan_db: scan db * @entry: input scan cache entry * @dup_node: node before which new entry to be added * * ref_cnt is taken for dup_node, caller should release ref taken * if returns true. * * Return: bool */ static bool scm_find_duplicate(struct scan_dbs *scan_db, struct scan_cache_entry *entry, struct scan_cache_node **dup_node) { 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_copy_info_from_dup_entry(scan_db, entry, cur_node); *dup_node = cur_node; 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 * @psoc: psoc ptr * @pdev: pdev pointer * @scan_params: new received entry * * Return: QDF_STATUS */ static QDF_STATUS scm_add_update_entry(struct wlan_objmgr_psoc *psoc, struct wlan_objmgr_pdev *pdev, struct scan_cache_entry *scan_params) { struct scan_cache_node *dup_node = NULL; struct scan_cache_node *scan_node = NULL; bool is_dup_found = false; QDF_STATUS status; struct scan_dbs *scan_db; struct wlan_scan_obj *scan_obj; scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return QDF_STATUS_E_INVAL; } scan_obj = wlan_psoc_get_scan_obj(psoc); if (!scan_obj) { scm_err("scan_obj is NULL"); return QDF_STATUS_E_INVAL; } if (scan_params->frm_subtype == MGMT_SUBTYPE_PROBE_RESP && !scan_params->ie_list.ssid) scm_debug("Probe resp doesn't contain SSID"); if (scan_params->ie_list.csa || scan_params->ie_list.xcsa || scan_params->ie_list.cswrp) scm_debug("CSA IE present for BSSID: %pM", scan_params->bssid.bytes); is_dup_found = scm_find_duplicate(scan_db, scan_params, &dup_node); if (scan_obj->cb.inform_beacon) scan_obj->cb.inform_beacon(pdev, scan_params); if (scan_db->num_entries >= MAX_SCAN_CACHE_SIZE) { status = scm_flush_oldest_entry(scan_db); if (QDF_IS_STATUS_ERROR(status)) { /* release ref taken for dup node */ if (is_dup_found) scm_scan_entry_put_ref(scan_db, dup_node, true); return status; } } scan_node = qdf_mem_malloc(sizeof(*scan_node)); if (!scan_node) { /* release ref taken for dup node */ if (is_dup_found) scm_scan_entry_put_ref(scan_db, dup_node, true); return QDF_STATUS_E_NOMEM; } scan_node->entry = scan_params; qdf_spin_lock_bh(&scan_db->scan_db_lock); scm_add_scan_node(scan_db, scan_node, dup_node); if (is_dup_found) { /* release ref taken for dup node and delete it */ scm_scan_entry_del(scan_db, dup_node); scm_scan_entry_put_ref(scan_db, dup_node, false); } qdf_spin_unlock_bh(&scan_db->scan_db_lock); return QDF_STATUS_SUCCESS; } QDF_STATUS scm_handle_bcn_probe(struct scheduler_msg *msg) { struct scan_bcn_probe_event *bcn; struct wlan_objmgr_psoc *psoc; struct wlan_objmgr_pdev *pdev = NULL; struct scan_cache_entry *scan_entry; struct wlan_scan_obj *scan_obj; qdf_list_t *scan_list = NULL; QDF_STATUS status = QDF_STATUS_SUCCESS; uint32_t list_count, i; qdf_list_node_t *next_node = NULL; struct scan_cache_node *scan_node; struct wlan_frame_hdr *hdr = NULL; bcn = msg->bodyptr; if (!bcn) { scm_err("bcn is NULL"); return QDF_STATUS_E_INVAL; } if (!bcn->rx_data) { scm_err("rx_data iS NULL"); status = QDF_STATUS_E_INVAL; goto free_nbuf; } if (!bcn->buf) { scm_err("buf is NULL"); status = QDF_STATUS_E_INVAL; goto free_nbuf; } hdr = (struct wlan_frame_hdr *)qdf_nbuf_data(bcn->buf); psoc = bcn->psoc; pdev = wlan_objmgr_get_pdev_by_id(psoc, bcn->rx_data->pdev_id, WLAN_SCAN_ID); if (!pdev) { scm_err("pdev is NULL"); status = QDF_STATUS_E_INVAL; goto free_nbuf; } scan_obj = wlan_psoc_get_scan_obj(psoc); if (!scan_obj) { scm_err("scan_obj is NULL"); status = QDF_STATUS_E_INVAL; goto free_nbuf; } if (qdf_nbuf_len(bcn->buf) <= (sizeof(struct wlan_frame_hdr) + offsetof(struct wlan_bcn_frame, ie))) { scm_debug("invalid beacon/probe length"); status = QDF_STATUS_E_INVAL; goto free_nbuf; } if (bcn->frm_type == MGMT_SUBTYPE_BEACON && utils_is_dfs_ch(pdev, bcn->rx_data->channel)) { util_scan_add_hidden_ssid(pdev, bcn->buf); } scan_list = util_scan_unpack_beacon_frame(pdev, qdf_nbuf_data(bcn->buf), qdf_nbuf_len(bcn->buf), bcn->frm_type, bcn->rx_data); if (!scan_list || qdf_list_empty(scan_list)) { scm_debug("failed to unpack %d frame BSSID: %pM", bcn->frm_type, hdr->i_addr3); status = QDF_STATUS_E_INVAL; goto free_nbuf; } list_count = qdf_list_size(scan_list); for (i = 0; i < list_count; i++) { status = qdf_list_remove_front(scan_list, &next_node); if (QDF_IS_STATUS_ERROR(status) || next_node == NULL) { scm_debug("list remove failure i:%d, lsize:%d, BSSID: %pM", i, list_count, hdr->i_addr3); status = QDF_STATUS_E_INVAL; goto free_nbuf; } scan_node = qdf_container_of(next_node, struct scan_cache_node, node); scan_entry = scan_node->entry; scm_nofl_debug("Received %s from BSSID: %pM tsf_delta = %u Seq Num: %d ssid:%.*s, rssi: %d pdev_id = %d", (bcn->frm_type == MGMT_SUBTYPE_PROBE_RESP) ? "Probe Rsp" : "Beacon", scan_entry->bssid.bytes, scan_entry->tsf_delta, scan_entry->seq_num, scan_entry->ssid.length, scan_entry->ssid.ssid, scan_entry->rssi_raw, wlan_objmgr_pdev_get_pdev_id(pdev)); if (scan_obj->drop_bcn_on_chan_mismatch && scan_entry->channel_mismatch) { scm_debug("Drop frame, as channel mismatch Received for from BSSID: %pM Seq Num: %d", scan_entry->bssid.bytes, scan_entry->seq_num); util_scan_free_cache_entry(scan_entry); qdf_mem_free(scan_node); continue; } if (scan_obj->cb.update_beacon) scan_obj->cb.update_beacon(pdev, scan_entry); if (wlan_reg_11d_enabled_on_host(psoc)) scm_11d_handle_country_info(psoc, pdev, scan_entry); status = scm_add_update_entry(psoc, pdev, scan_entry); if (QDF_IS_STATUS_ERROR(status)) { scm_debug("failed to add entry for BSSID: %pM Seq Num: %d", scan_entry->bssid.bytes, scan_entry->seq_num); util_scan_free_cache_entry(scan_entry); qdf_mem_free(scan_node); continue; } qdf_mem_free(scan_node); } free_nbuf: if (scan_list) qdf_mem_free(scan_list); if (bcn->psoc) wlan_objmgr_psoc_release_ref(bcn->psoc, WLAN_SCAN_ID); if (pdev) wlan_objmgr_pdev_release_ref(pdev, WLAN_SCAN_ID); if (bcn->rx_data) qdf_mem_free(bcn->rx_data); if (bcn->buf) qdf_nbuf_free(bcn->buf); qdf_mem_free(bcn); return status; } /** * scm_list_insert_sorted() - add the entries in scan_list in sorted way * @psoc: psoc ptr * @filter: scan filter * @scan_node: node entry to be inserted * @scan_list: Temp scan list * * Add the entries in scan_list in sorted way considering * cap_val and prefer val. The node is copy of original scan entry and * thus no lock is required. * * Return: void */ static void scm_list_insert_sorted(struct wlan_objmgr_psoc *psoc, struct scan_filter *filter, struct scan_cache_node *scan_node, qdf_list_t *scan_list) { struct scan_cache_node *cur_node; qdf_list_node_t *cur_lst = NULL, *next_lst = NULL; struct scan_default_params *params; int pcl_chan_weight = 0; params = wlan_scan_psoc_get_def_params(psoc); if (!params) { scm_err("wlan_scan_psoc_get_def_params failed"); return; } if (filter->num_of_pcl_channels > 0 && (scan_node->entry->rssi_raw > SCM_PCL_RSSI_THRESHOLD)) { if (scm_get_pcl_weight_of_channel( scan_node->entry->channel.chan_idx, filter, &pcl_chan_weight, filter->pcl_weight_list)) { scm_debug("pcl channel %d pcl_chan_weight %d", scan_node->entry->channel.chan_idx, pcl_chan_weight); } } if (params->is_bssid_hint_priority && !qdf_mem_cmp(filter->bssid_hint.bytes, scan_node->entry->bssid.bytes, QDF_MAC_ADDR_SIZE)) scan_node->entry->bss_score = BEST_CANDIDATE_MAX_BSS_SCORE; else scm_calculate_bss_score(psoc, params, scan_node->entry, pcl_chan_weight); if (qdf_list_empty(scan_list)) { qdf_list_insert_front(scan_list, &scan_node->node); return; } qdf_list_peek_front(scan_list, &cur_lst); while (cur_lst) { cur_node = qdf_container_of(cur_lst, struct scan_cache_node, node); if (scm_is_better_bss(params, scan_node->entry, cur_node->entry)) { qdf_list_insert_before(scan_list, &scan_node->node, &cur_node->node); break; } qdf_list_peek_next(scan_list, cur_lst, &next_lst); cur_lst = next_lst; next_lst = NULL; } if (!cur_lst) qdf_list_insert_back(scan_list, &scan_node->node); } /** * scm_scan_apply_filter_get_entry() - apply filter and get the * scan entry * @psoc: psoc pointer * @db_entry: scan entry * @filter: filter to be applied * @scan_list: scan list to which entry is added * * Return: QDF_STATUS */ static QDF_STATUS scm_scan_apply_filter_get_entry(struct wlan_objmgr_psoc *psoc, struct scan_cache_entry *db_entry, struct scan_filter *filter, qdf_list_t *scan_list) { struct scan_cache_node *scan_node = NULL; struct security_info security = {0}; bool match; if (!filter) match = true; else match = scm_filter_match(psoc, db_entry, filter, &security); if (!match) return QDF_STATUS_SUCCESS; scan_node = qdf_mem_malloc_atomic(sizeof(*scan_node)); if (!scan_node) return QDF_STATUS_E_NOMEM; scan_node->entry = util_scan_copy_cache_entry(db_entry); if (!scan_node->entry) { qdf_mem_free(scan_node); return QDF_STATUS_E_NOMEM; } qdf_mem_copy(&scan_node->entry->neg_sec_info, &security, sizeof(scan_node->entry->neg_sec_info)); if (!filter || !filter->bss_scoring_required) qdf_list_insert_front(scan_list, &scan_node->node); else scm_list_insert_sorted(psoc, filter, scan_node, scan_list); return QDF_STATUS_SUCCESS; } /** * scm_get_results() - Iterate and get scan results * @psoc: psoc ptr * @scan_db: scan db * @filter: filter to be applied * @scan_list: scan list to which entry is added * * Return: void */ static void scm_get_results(struct wlan_objmgr_psoc *psoc, struct scan_dbs *scan_db, struct scan_filter *filter, qdf_list_t *scan_list) { int i, count; struct scan_cache_node *cur_node; 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); count = qdf_list_size(&scan_db->scan_hash_tbl[i]); if (!count) continue; while (cur_node) { scm_scan_apply_filter_get_entry(psoc, cur_node->entry, filter, scan_list); next_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[i], cur_node); cur_node = next_node; } } } QDF_STATUS scm_purge_scan_results(qdf_list_t *scan_list) { QDF_STATUS status; struct scan_cache_node *cur_node; qdf_list_node_t *cur_lst = NULL, *next_lst = NULL; if (!scan_list) { scm_err("scan_result is NULL"); return QDF_STATUS_E_INVAL; } status = qdf_list_peek_front(scan_list, &cur_lst); while (cur_lst) { qdf_list_peek_next( scan_list, cur_lst, &next_lst); cur_node = qdf_container_of(cur_lst, struct scan_cache_node, node); status = qdf_list_remove_node(scan_list, cur_lst); if (QDF_IS_STATUS_SUCCESS(status)) { util_scan_free_cache_entry(cur_node->entry); qdf_mem_free(cur_node); } cur_lst = next_lst; next_lst = NULL; } qdf_list_destroy(scan_list); qdf_mem_free(scan_list); return status; } qdf_list_t *scm_get_scan_result(struct wlan_objmgr_pdev *pdev, struct scan_filter *filter) { struct wlan_objmgr_psoc *psoc; struct scan_dbs *scan_db; qdf_list_t *tmp_list; if (!pdev) { scm_err("pdev is NULL"); return NULL; } psoc = wlan_pdev_get_psoc(pdev); if (!psoc) { scm_err("psoc is NULL"); return NULL; } scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return NULL; } tmp_list = qdf_mem_malloc_atomic(sizeof(*tmp_list)); if (!tmp_list) { scm_err("failed tp allocate scan_result"); return NULL; } qdf_list_create(tmp_list, MAX_SCAN_CACHE_SIZE); scm_age_out_entries(psoc, scan_db); scm_get_results(psoc, scan_db, filter, tmp_list); return tmp_list; } /** * scm_iterate_db_and_call_func() - iterate and call the func * @scan_db: scan db * @func: func to be called * @arg: func arg * * Return: QDF_STATUS */ static QDF_STATUS scm_iterate_db_and_call_func(struct scan_dbs *scan_db, scan_iterator_func func, void *arg) { int i; QDF_STATUS status = QDF_STATUS_SUCCESS; struct scan_cache_node *cur_node; struct scan_cache_node *next_node = NULL; if (!func) return QDF_STATUS_E_INVAL; 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) { status = func(arg, cur_node->entry); if (QDF_IS_STATUS_ERROR(status)) { scm_scan_entry_put_ref(scan_db, cur_node, true); return status; } next_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[i], cur_node); cur_node = next_node; } } return status; } QDF_STATUS scm_iterate_scan_db(struct wlan_objmgr_pdev *pdev, scan_iterator_func func, void *arg) { struct wlan_objmgr_psoc *psoc; struct scan_dbs *scan_db; QDF_STATUS status; if (!func) { scm_err("func is NULL"); return QDF_STATUS_E_INVAL; } if (!pdev) { scm_err("pdev is NULL"); return QDF_STATUS_E_INVAL; } psoc = wlan_pdev_get_psoc(pdev); if (!psoc) { scm_err("psoc is NULL"); return QDF_STATUS_E_INVAL; } scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return QDF_STATUS_E_INVAL; } scm_age_out_entries(psoc, scan_db); status = scm_iterate_db_and_call_func(scan_db, func, arg); return status; } /** * scm_scan_apply_filter_flush_entry() -flush scan entries depending * on filter * @psoc: psoc ptr * @scan_db: scan db * @db_node: node on which filters are applied * @filter: filter to be applied * * Return: QDF_STATUS */ static QDF_STATUS scm_scan_apply_filter_flush_entry(struct wlan_objmgr_psoc *psoc, struct scan_dbs *scan_db, struct scan_cache_node *db_node, struct scan_filter *filter) { struct security_info security = {0}; bool match; if (!filter) match = true; else match = scm_filter_match(psoc, db_node->entry, filter, &security); if (!match) return QDF_STATUS_SUCCESS; qdf_spin_lock_bh(&scan_db->scan_db_lock); scm_scan_entry_del(scan_db, db_node); qdf_spin_unlock_bh(&scan_db->scan_db_lock); return QDF_STATUS_SUCCESS; } /** * scm_flush_scan_entries() - API to flush scan entries depending on filters * @psoc: psoc ptr * @scan_db: scan db * @filter: filter * * Return: void */ static void scm_flush_scan_entries(struct wlan_objmgr_psoc *psoc, struct scan_dbs *scan_db, struct scan_filter *filter) { int i; struct scan_cache_node *cur_node; 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_scan_apply_filter_flush_entry(psoc, scan_db, cur_node, filter); next_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[i], cur_node); cur_node = next_node; } } } QDF_STATUS scm_flush_results(struct wlan_objmgr_pdev *pdev, struct scan_filter *filter) { struct wlan_objmgr_psoc *psoc; struct scan_dbs *scan_db; QDF_STATUS status = QDF_STATUS_SUCCESS; if (!pdev) { scm_err("pdev is NULL"); return QDF_STATUS_E_INVAL; } psoc = wlan_pdev_get_psoc(pdev); if (!psoc) { scm_err("psoc is NULL"); return QDF_STATUS_E_INVAL; } scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return QDF_STATUS_E_INVAL; } scm_flush_scan_entries(psoc, scan_db, filter); return status; } /** * scm_filter_channels() - Remove entries not belonging to channel list * @scan_db: scan db * @db_node: node on which filters are applied * @chan_list: valid channel list * @num_chan: number of channels * * Return: QDF_STATUS */ static void scm_filter_channels(struct scan_dbs *scan_db, struct scan_cache_node *db_node, uint8_t *chan_list, uint32_t num_chan) { int i; bool match = false; for (i = 0; i < num_chan; i++) { if (chan_list[i] == util_scan_entry_channel_num(db_node->entry)) { match = true; break; } } if (!match) { qdf_spin_lock_bh(&scan_db->scan_db_lock); scm_scan_entry_del(scan_db, db_node); qdf_spin_unlock_bh(&scan_db->scan_db_lock); } } void scm_filter_valid_channel(struct wlan_objmgr_pdev *pdev, uint8_t *chan_list, uint32_t num_chan) { int i; struct wlan_objmgr_psoc *psoc; struct scan_dbs *scan_db; struct scan_cache_node *cur_node; struct scan_cache_node *next_node = NULL; scm_debug("num_chan = %d", num_chan); if (!pdev) { scm_err("pdev is NULL"); return; } psoc = wlan_pdev_get_psoc(pdev); if (!psoc) { scm_err("psoc is NULL"); return; } scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return; } 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_filter_channels(scan_db, cur_node, chan_list, num_chan); next_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[i], cur_node); cur_node = next_node; } } } QDF_STATUS scm_scan_register_bcn_cb(struct wlan_objmgr_psoc *psoc, update_beacon_cb cb, enum scan_cb_type type) { struct wlan_scan_obj *scan_obj; scan_obj = wlan_psoc_get_scan_obj(psoc); if (!scan_obj) { scm_err("scan obj is NULL"); return QDF_STATUS_E_INVAL; } switch (type) { case SCAN_CB_TYPE_INFORM_BCN: scan_obj->cb.inform_beacon = cb; break; case SCAN_CB_TYPE_UPDATE_BCN: scan_obj->cb.update_beacon = cb; break; default: scm_err("invalid cb type %d", type); } return QDF_STATUS_SUCCESS; } QDF_STATUS scm_db_init(struct wlan_objmgr_psoc *psoc) { int i, j; struct scan_dbs *scan_db; if (!psoc) { scm_err("psoc is NULL"); return QDF_STATUS_E_INVAL; } /* Initialize the scan database per pdev */ for (i = 0; i < WLAN_UMAC_MAX_PDEVS; i++) { scan_db = wlan_pdevid_get_scan_db(psoc, i); if (!scan_db) { scm_err("scan_db is NULL %d", i); continue; } scan_db->num_entries = 0; qdf_spinlock_create(&scan_db->scan_db_lock); for (j = 0; j < SCAN_HASH_SIZE; j++) qdf_list_create(&scan_db->scan_hash_tbl[j], MAX_SCAN_CACHE_SIZE); } return QDF_STATUS_SUCCESS; } QDF_STATUS scm_db_deinit(struct wlan_objmgr_psoc *psoc) { int i, j; struct scan_dbs *scan_db; if (!psoc) { scm_err("scan obj is NULL"); return QDF_STATUS_E_INVAL; } /* Initialize the scan database per pdev */ for (i = 0; i < WLAN_UMAC_MAX_PDEVS; i++) { scan_db = wlan_pdevid_get_scan_db(psoc, i); if (!scan_db) { scm_err("scan_db is NULL %d", i); continue; } scm_flush_scan_entries(psoc, scan_db, NULL); for (j = 0; j < SCAN_HASH_SIZE; j++) qdf_list_destroy(&scan_db->scan_hash_tbl[j]); qdf_spinlock_destroy(&scan_db->scan_db_lock); } return QDF_STATUS_SUCCESS; } QDF_STATUS scm_update_scan_mlme_info(struct wlan_objmgr_pdev *pdev, struct scan_cache_entry *entry) { uint8_t hash_idx; struct scan_dbs *scan_db; struct scan_cache_node *cur_node; struct scan_cache_node *next_node = NULL; struct wlan_objmgr_psoc *psoc; psoc = wlan_pdev_get_psoc(pdev); if (!psoc) { scm_err("psoc is NULL"); return QDF_STATUS_E_INVAL; } scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return QDF_STATUS_E_INVAL; } 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)) { /* Acquire db lock to prevent simultaneous update */ qdf_spin_lock_bh(&scan_db->scan_db_lock); scm_update_mlme_info(entry, cur_node->entry); qdf_spin_unlock_bh(&scan_db->scan_db_lock); scm_scan_entry_put_ref(scan_db, cur_node, true); return QDF_STATUS_SUCCESS; } next_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[hash_idx], cur_node); cur_node = next_node; } return QDF_STATUS_E_INVAL; } QDF_STATUS scm_scan_update_mlme_by_bssinfo(struct wlan_objmgr_pdev *pdev, struct bss_info *bss_info, struct mlme_info *mlme) { uint8_t hash_idx; struct scan_dbs *scan_db; struct scan_cache_node *cur_node; struct scan_cache_node *next_node = NULL; struct wlan_objmgr_psoc *psoc; struct scan_cache_entry *entry; psoc = wlan_pdev_get_psoc(pdev); if (!psoc) { scm_err("psoc is NULL"); return QDF_STATUS_E_INVAL; } scan_db = wlan_pdev_get_scan_db(psoc, pdev); if (!scan_db) { scm_err("scan_db is NULL"); return QDF_STATUS_E_INVAL; } hash_idx = SCAN_GET_HASH(bss_info->bssid.bytes); cur_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[hash_idx], NULL); while (cur_node) { entry = cur_node->entry; if (qdf_is_macaddr_equal(&bss_info->bssid, &entry->bssid) && (util_is_ssid_match(&bss_info->ssid, &entry->ssid)) && (bss_info->chan == entry->channel.chan_idx)) { /* Acquire db lock to prevent simultaneous update */ qdf_spin_lock_bh(&scan_db->scan_db_lock); qdf_mem_copy(&entry->mlme_info, mlme, sizeof(struct mlme_info)); scm_scan_entry_put_ref(scan_db, cur_node, false); qdf_spin_unlock_bh(&scan_db->scan_db_lock); return QDF_STATUS_SUCCESS; } next_node = scm_get_next_node(scan_db, &scan_db->scan_hash_tbl[hash_idx], cur_node); cur_node = next_node; } return QDF_STATUS_E_INVAL; }