blob: cad70430474a216c8d759ab432ba0f76f7f4ecd2 [file] [log] [blame]
/*
* Copyright (c) 2017-2021 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. 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 6ghz scan manager functionality
*/
#include "wlan_scan_main.h"
#include "wlan_utility.h"
#include <wlan_reg_services_api.h>
#include "wlan_scan_manager.h"
/* Beacon/probe weightage multiplier */
#define BCN_PROBE_WEIGHTAGE 5
/* maximum number of 6ghz hints can be sent per scan request */
#define MAX_HINTS_PER_SCAN_REQ 15
/* Saved profile weightage multiplier */
#define SAVED_PROFILE_WEIGHTAGE 10
#ifdef FEATURE_6G_SCAN_CHAN_SORT_ALGO
/**
* scm_sort_6ghz_channel_list() - Sort the 6ghz channels based on weightage
* @vdev: vdev on which scan request is issued
* @chan_list: channel info of the scan request
*
* Calculate weightage of each channel based on beacon weightage and saved
* profile weightage. Sort the channels based on this weight in descending order
* to scan the most preferred channels first compared other 6ghz channels.
*
* Return: None
*/
static void
scm_sort_6ghz_channel_list(struct wlan_objmgr_vdev *vdev,
struct chan_list *chan_list)
{
uint8_t i, j = 0, max, tmp_list_count;
struct meta_rnr_channel *channel;
struct chan_info temp_list[MAX_6GHZ_CHANNEL];
struct rnr_chan_weight *rnr_chan_info, temp;
uint32_t weight;
struct wlan_objmgr_psoc *psoc;
psoc = wlan_vdev_get_psoc(vdev);
if (!psoc) {
scm_err("Psoc is NULL");
return;
}
for (i = 0; i < chan_list->num_chan; i++)
if (WLAN_REG_IS_6GHZ_CHAN_FREQ(chan_list->chan[i].freq))
temp_list[j++] = chan_list->chan[i];
tmp_list_count = j;
scm_debug("Total 6ghz channels %d", tmp_list_count);
/* No Need to sort if the 6ghz channels are less than or one */
if (tmp_list_count <= 1)
return;
rnr_chan_info = qdf_mem_malloc(sizeof(*rnr_chan_info) * tmp_list_count);
if (!rnr_chan_info)
return;
/* compute the weightage */
for (i = 0, j = 0; i < tmp_list_count; i++) {
channel = scm_get_chan_meta(psoc, temp_list[i].freq);
if (!channel)
continue;
weight = channel->bss_beacon_probe_count * BCN_PROBE_WEIGHTAGE +
channel->saved_profile_count * SAVED_PROFILE_WEIGHTAGE;
rnr_chan_info[j].weight = weight;
rnr_chan_info[j].chan_freq = temp_list[i].freq;
rnr_chan_info[j].phymode = temp_list[i].phymode;
rnr_chan_info[j].flags = temp_list[i].flags;
j++;
/*
* Log the info only if weight or bss_beacon_probe_count are
* non-zero to avoid excessive logging.
*/
if (weight || channel->bss_beacon_probe_count)
scm_debug("Freq %d weight %d bcn_cnt %d",
temp_list[i].freq, weight,
channel->bss_beacon_probe_count);
}
/* Sort the channel using selection sort - descending order */
for (i = 0; i < tmp_list_count - 1; i++) {
max = i;
for (j = i + 1; j < tmp_list_count; j++) {
if (rnr_chan_info[j].weight >
rnr_chan_info[max].weight)
max = j;
}
if (max != i) {
qdf_mem_copy(&temp, &rnr_chan_info[max],
sizeof(*rnr_chan_info));
qdf_mem_copy(&rnr_chan_info[max], &rnr_chan_info[i],
sizeof(*rnr_chan_info));
qdf_mem_copy(&rnr_chan_info[i], &temp,
sizeof(*rnr_chan_info));
}
}
/* update the 6g list based on the weightage */
for (i = 0, j = 0; (i < NUM_CHANNELS && j < tmp_list_count); i++)
if (wlan_reg_is_6ghz_chan_freq(chan_list->chan[i].freq)) {
chan_list->chan[i].freq = rnr_chan_info[j].chan_freq;
chan_list->chan[i].flags = rnr_chan_info[j].flags;
chan_list->chan[i].phymode = rnr_chan_info[j++].phymode;
}
qdf_mem_free(rnr_chan_info);
}
static void scm_update_rnr_info(struct wlan_objmgr_psoc *psoc,
struct scan_start_request *req)
{
uint8_t i, num_bssid = 0, num_ssid = 0;
uint8_t total_count = MAX_HINTS_PER_SCAN_REQ;
uint32_t freq;
struct meta_rnr_channel *chan;
qdf_list_node_t *cur_node, *next_node = NULL;
struct scan_rnr_node *rnr_node;
struct chan_list *chan_list;
QDF_STATUS status;
bool hint = false;
if (!req)
return;
chan_list = &req->scan_req.chan_list;
for (i = 0; i < chan_list->num_chan; i++) {
freq = chan_list->chan[i].freq;
chan = scm_get_chan_meta(psoc, freq);
if (!chan || qdf_list_empty(&chan->rnr_list))
continue;
qdf_list_peek_front(&chan->rnr_list, &cur_node);
while (cur_node && total_count) {
rnr_node = qdf_container_of(cur_node,
struct scan_rnr_node,
node);
if (!qdf_is_macaddr_zero(&rnr_node->entry.bssid) &&
req->scan_req.num_hint_bssid <
WLAN_SCAN_MAX_HINT_BSSID) {
qdf_mem_copy(&req->scan_req.hint_bssid[
num_bssid].bssid,
&rnr_node->entry.bssid,
QDF_MAC_ADDR_SIZE);
req->scan_req.hint_bssid[
num_bssid++].freq_flags = freq << 16;
req->scan_req.num_hint_bssid++;
hint = true;
}
if (rnr_node->entry.short_ssid &&
req->scan_req.num_hint_s_ssid <
WLAN_SCAN_MAX_HINT_S_SSID) {
req->scan_req.hint_s_ssid[
num_ssid].short_ssid =
rnr_node->entry.short_ssid;
req->scan_req.hint_s_ssid[
num_ssid++].freq_flags = freq << 16;
req->scan_req.num_hint_s_ssid++;
hint = true;
}
if (hint) {
total_count--;
hint = false;
}
status = qdf_list_peek_next(&chan->rnr_list, cur_node,
&next_node);
if (QDF_IS_STATUS_ERROR(status))
break;
cur_node = next_node;
next_node = NULL;
}
}
}
/**
* scm_add_rnr_info() - Add the cached RNR info to scan request
* @vdev: vdev on which scan request is issued
* @req: Scan start request
*
* Fetch the cached RNR info from scan db and update it to the scan request to
* include RNR channels in the scan request.
*
* Return: None
*/
static void scm_add_rnr_info(struct wlan_objmgr_pdev *pdev,
struct scan_start_request *req)
{
struct wlan_objmgr_psoc *psoc;
struct channel_list_db *rnr_db;
psoc = wlan_pdev_get_psoc(pdev);
if (!psoc)
return;
rnr_db = scm_get_rnr_channel_db(psoc);
if (!rnr_db)
return;
rnr_db->scan_count++;
if (rnr_db->scan_count >= RNR_UPDATE_SCAN_CNT_THRESHOLD) {
rnr_db->scan_count = 0;
scm_rnr_db_flush(psoc);
scm_update_rnr_from_scan_cache(pdev);
}
scm_update_rnr_info(psoc, req);
}
#else
static void
scm_sort_6ghz_channel_list(struct wlan_objmgr_vdev *vdev,
struct chan_list *chan_list)
{
}
static void scm_add_rnr_info(struct wlan_objmgr_pdev *pdev,
struct scan_start_request *req)
{
}
#endif
static inline bool
scm_is_full_scan_by_userspace(struct chan_list *chan_list)
{
return (chan_list->num_chan >= FULL_SCAN_CH_COUNT_MIN_BY_USERSPACE);
}
static inline bool
scm_is_scan_type_exempted_from_optimization(struct scan_start_request *req)
{
/* Dont modify the channel list for RRM type*/
return (req->scan_req.scan_type == SCAN_TYPE_RRM);
}
/**
* scm_add_all_valid_6g_channels() - Add all valid 6g channels to scan request
* @vdev: vdev on which scan request is issued
* @req: Scan start request
* @num_scan_ch: Total number of scan channels
* @is_colocated_6ghz_scan_enabled: colocated 6ghz scan flag enabled in scan req
*
* If colocated 6ghz scan flag present in host scan request or at least one 6G
* channel is present in the host scan request, then this API
* fills all remaining (other than channel(s) resent in host scan req) valid
* 6 GHz channel(s) to scan requests channel list and set the flag
* FLAG_SCAN_ONLY_IF_RNR_FOUND for each of those added channels.
* By this driver allows Firmware to scan 6G channels based on RNR IEs only.
*
* Return: None
*/
void scm_add_all_valid_6g_channels(struct wlan_objmgr_pdev *pdev,
struct chan_list *chan_list,
uint8_t *num_scan_ch,
bool is_colocated_6ghz_scan_enabled)
{
uint8_t i, j;
enum channel_enum freq_idx;
struct regulatory_channel *cur_chan_list;
bool found;
QDF_STATUS status;
uint8_t temp_num_chan = 0;
if (!is_colocated_6ghz_scan_enabled) {
scm_debug("flag is not set in scan req");
return;
}
cur_chan_list = qdf_mem_malloc(NUM_CHANNELS *
sizeof(struct regulatory_channel));
if (!cur_chan_list)
return;
status = wlan_reg_get_current_chan_list(pdev, cur_chan_list);
if (QDF_IS_STATUS_ERROR(status)) {
qdf_mem_free(cur_chan_list);
scm_debug("Failed to get cur_chan list");
return;
}
freq_idx =
wlan_reg_get_chan_enum_for_freq(wlan_reg_min_6ghz_chan_freq());
if (reg_is_chan_enum_invalid(freq_idx))
return;
scm_debug("freq_idx:%d", freq_idx);
temp_num_chan = chan_list->num_chan;
for (i = freq_idx; i < NUM_CHANNELS; i++) {
found = false;
for (j = 0; j < temp_num_chan; j++) {
if (cur_chan_list[i].center_freq ==
chan_list->chan[j].freq) {
found = true;
break;
}
}
if (!found && cur_chan_list[i].state != CHANNEL_STATE_DISABLE &&
cur_chan_list[i].state != CHANNEL_STATE_INVALID) {
chan_list->chan[chan_list->num_chan].freq =
cur_chan_list[i].center_freq;
chan_list->chan[chan_list->num_chan].flags =
FLAG_SCAN_ONLY_IF_RNR_FOUND;
chan_list->num_chan++;
}
}
scm_debug("prev num_chan:%d, current num_chan:%d", temp_num_chan,
chan_list->num_chan);
*num_scan_ch = chan_list->num_chan;
qdf_mem_free(cur_chan_list);
}
static void
scm_copy_valid_channels(struct wlan_objmgr_psoc *psoc,
enum scan_mode_6ghz scan_mode,
struct scan_start_request *req,
uint8_t *num_scan_ch)
{
uint8_t i, num_ch = *num_scan_ch;
struct chan_list *chan_list = &req->scan_req.chan_list;
qdf_freq_t freq;
switch (scan_mode) {
case SCAN_MODE_6G_NO_CHANNEL:
/* Don't add any 6g channels */
for (i = 0; i < chan_list->num_chan; i++)
if (!wlan_reg_is_6ghz_chan_freq(
chan_list->chan[i].freq))
chan_list->chan[num_ch++] =
chan_list->chan[i];
break;
case SCAN_MODE_6G_PSC_CHANNEL:
case SCAN_MODE_6G_PSC_DUTY_CYCLE:
/*
* Filter out non-PSC 6g channels if firmware doesn't
* supports RNR_ONLY scan flag/feature and the scan type is
* allowed to be optimized.
*/
if (!scm_is_6ghz_scan_optimization_supported(psoc) &&
!scm_is_scan_type_exempted_from_optimization(req)) {
for (i = 0; i < chan_list->num_chan; i++) {
freq = chan_list->chan[i].freq;
if (!wlan_reg_is_6ghz_chan_freq(freq) ||
(wlan_reg_is_6ghz_chan_freq(freq) &&
wlan_reg_is_6ghz_psc_chan_freq(freq)))
chan_list->chan[num_ch++] =
chan_list->chan[i];
}
break;
}
/*
* Consider the complete channel list if firmware supports
* RNR_ONLY scan flag/feature.
*/
/* fallthrough */
default:
/*
* Allow all 2g/5g/6g channels. Below are also covered in this
* 1. SCAN_MODE_6G_ALL_CHANNEL: Copy all channels and RNR flag
* won't be set for any channel.
* 2. SCAN_MODE_6G_PSC_CHANNEL: Copy all channels and RNR flag
* will be set for non-PSC.
* 3. SCAN_MODE_6G_PSC_DUTY_CYCLE: Copy all channels and RNR
* flag will be set for non-PSC for all scans and RNR flag
* will be set for PSC channels only for duty cycle scan.
*/
num_ch = chan_list->num_chan;
}
*num_scan_ch = num_ch;
}
static inline void
scm_set_rnr_flag_non_psc_6g_ch(struct chan_info *chan, uint8_t num_chan)
{
uint8_t i;
for (i = 0; i < num_chan; i++)
if (wlan_reg_is_6ghz_chan_freq(chan[i].freq) &&
!wlan_reg_is_6ghz_psc_chan_freq(chan[i].freq))
chan[i].flags = FLAG_SCAN_ONLY_IF_RNR_FOUND;
}
static inline void
scm_set_rnr_flag_all_6g_ch(struct chan_info *chan, uint8_t num_chan)
{
uint8_t i;
for (i = 0; i < num_chan; i++)
if (wlan_reg_is_6ghz_chan_freq(chan[i].freq))
chan[i].flags = FLAG_SCAN_ONLY_IF_RNR_FOUND;
}
static bool scm_is_duty_cycle_scan(struct wlan_scan_obj *scan_obj)
{
bool duty_cycle = false;
scan_obj->duty_cycle_cnt_6ghz++;
if (scan_obj->duty_cycle_cnt_6ghz == 1)
duty_cycle = true;
if (scan_obj->scan_def.duty_cycle_6ghz == scan_obj->duty_cycle_cnt_6ghz)
scan_obj->duty_cycle_cnt_6ghz = 0;
return duty_cycle;
}
inline bool
scm_is_6ghz_scan_optimization_supported(struct wlan_objmgr_psoc *psoc)
{
return wlan_psoc_nif_fw_ext_cap_get(psoc,
WLAN_SOC_CEXT_SCAN_PER_CH_CONFIG);
}
void scm_add_channel_flags(struct wlan_objmgr_vdev *vdev,
struct chan_list *chan_list,
uint8_t *num_chan,
bool is_colocated_6ghz_scan_enabled,
bool is_pno_scan)
{
struct wlan_scan_obj *scan_obj;
enum scan_mode_6ghz scan_mode;
struct wlan_objmgr_pdev *pdev;
uint8_t num_scan_chan = *num_chan;
pdev = wlan_vdev_get_pdev(vdev);
if (!pdev)
return;
scan_obj = wlan_vdev_get_scan_obj(vdev);
if (!scan_obj) {
scm_err("scan_obj is NULL");
return;
}
scan_mode = scan_obj->scan_def.scan_mode_6g;
switch (scan_mode) {
case SCAN_MODE_6G_RNR_ONLY:
/*
* When the ini is set to SCAN_MODE_6G_RNR_ONLY
* always set RNR flag for all(PSC and non-PSC) channels.
*/
scm_set_rnr_flag_all_6g_ch(&chan_list->chan[0], num_scan_chan);
break;
case SCAN_MODE_6G_PSC_CHANNEL:
/*
* When the ini is set to SCAN_MODE_6G_PSC_CHANNEL,
* always set RNR flag for non-PSC channels.
*/
scm_set_rnr_flag_non_psc_6g_ch(&chan_list->chan[0],
num_scan_chan);
break;
case SCAN_MODE_6G_PSC_DUTY_CYCLE:
case SCAN_MODE_6G_ALL_DUTY_CYCLE:
if (!is_pno_scan && !scm_is_duty_cycle_scan(scan_obj))
scm_set_rnr_flag_all_6g_ch(&chan_list->chan[0],
num_scan_chan);
else if (scan_mode == SCAN_MODE_6G_PSC_DUTY_CYCLE) {
if (is_pno_scan)
scm_debug("Duty cycle scan not supported in pno");
scm_set_rnr_flag_non_psc_6g_ch(&chan_list->chan[0],
num_scan_chan);
}
fallthrough;
/* Even when the scan mode is SCAN_MODE_6G_PSC_DUTY_CYCLE or
* SCAN_MODE_6G_ALL_DUTY_CYCLE, it is better to add other 6 GHz
* channels to the channel list and set the bit
* FLAG_SCAN_ONLY_IF_RNR_FOUND for these new channels.
* This can help to find the APs which have co-located APs in
* given 2 GHz/5 GHz channels.
* Let it fallthrough as this is already addressed through the
* scan mode SCAN_MODE_6G_ALL_CHANNEL.
*/
case SCAN_MODE_6G_ALL_CHANNEL:
/*
* When the ini is set to SCAN_MODE_6G_ALL_CHANNEL,
* Host fills all remaining (other than channel(s) present in
* host scan req) valid 6 GHz channel(s) to scan requests and
* set the flag FLAG_SCAN_ONLY_IF_RNR_FOUND for each remaining
* channels.
*/
scm_add_all_valid_6g_channels(pdev, chan_list, num_chan,
is_colocated_6ghz_scan_enabled);
break;
default:
/*
* Don't set the RNR flag for SCAN_MODE_6G_NO_CHANNEL/
* SCAN_MODE_6G_RNR_ONLY
*/
break;
}
}
void
scm_update_6ghz_channel_list(struct scan_start_request *req,
struct wlan_scan_obj *scan_obj)
{
struct wlan_objmgr_vdev *vdev = req->vdev;
struct wlan_objmgr_pdev *pdev;
struct chan_list *chan_list = &req->scan_req.chan_list;
enum scan_mode_6ghz scan_mode;
uint8_t num_scan_ch = 0;
enum QDF_OPMODE op_mode;
struct wlan_objmgr_psoc *psoc;
pdev = wlan_vdev_get_pdev(vdev);
if (!pdev)
return;
psoc = wlan_pdev_get_psoc(pdev);
/* Dont update the channel list for not STA mode */
op_mode = wlan_vdev_mlme_get_opmode(req->vdev);
if (op_mode == QDF_SAP_MODE ||
op_mode == QDF_P2P_DEVICE_MODE ||
op_mode == QDF_P2P_CLIENT_MODE ||
op_mode == QDF_P2P_GO_MODE)
return;
scan_mode = scan_obj->scan_def.scan_mode_6g;
scm_debug("6g scan mode %d", scan_mode);
/*
* Host has learned RNR info/channels from previous scan. Add them to
* the scan request and don't set RNR_ONLY flag to scan them without
* optimization. Don't add RNR info if the scan type is exempted from
* optimization.
*/
if (scan_mode != SCAN_MODE_6G_NO_CHANNEL &&
scm_is_full_scan_by_userspace(chan_list) &&
!scm_is_scan_type_exempted_from_optimization(req))
scm_add_rnr_info(pdev, req);
/* copy all the channels given by userspace */
scm_copy_valid_channels(psoc, scan_mode, req, &num_scan_ch);
/* No more optimizations are needed in the below cases */
if (!scm_is_full_scan_by_userspace(chan_list) ||
!scm_is_6ghz_scan_optimization_supported(psoc) ||
scm_is_scan_type_exempted_from_optimization(req))
goto end;
scm_add_channel_flags(vdev, chan_list, &num_scan_ch,
req->scan_req.scan_policy_colocated_6ghz, false);
end:
chan_list->num_chan = num_scan_ch;
scm_sort_6ghz_channel_list(req->vdev, &req->scan_req.chan_list);
}