// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2005-2014 Intel Corporation
*/
#include <linux/slab.h>
#include <net/mac80211.h>
#include "iwl-trans.h"
#include "dev.h"
#include "calib.h"
#include "agn.h"
/*****************************************************************************
* INIT calibrations framework
*****************************************************************************/
/* Opaque calibration results */
struct iwl_calib_result {
struct list_head list;
size_t cmd_len;
struct iwl_calib_cmd cmd;
};
struct statistics_general_data {
u32 beacon_silence_rssi_a;
u32 beacon_silence_rssi_b;
u32 beacon_silence_rssi_c;
u32 beacon_energy_a;
u32 beacon_energy_b;
u32 beacon_energy_c;
};
int iwl_send_calib_results(struct iwl_priv *priv)
{
struct iwl_host_cmd hcmd = {
.id = REPLY_PHY_CALIBRATION_CMD,
};
struct iwl_calib_result *res;
list_for_each_entry(res, &priv->calib_results, list) {
int ret;
hcmd.len[0] = res->cmd_len;
hcmd.data[0] = &res->cmd;
hcmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY;
ret = iwl_dvm_send_cmd(priv, &hcmd);
if (ret) {
IWL_ERR(priv, "Error %d on calib cmd %d\n",
ret, res->cmd.hdr.op_code);
return ret;
}
}
return 0;
}
int iwl_calib_set(struct iwl_priv *priv,
const struct iwl_calib_cmd *cmd, size_t len)
{
struct iwl_calib_result *res, *tmp;
if (check_sub_overflow(len, sizeof(*cmd), &len))
return -ENOMEM;
res = kmalloc(struct_size(res, cmd.data, len), GFP_ATOMIC);
if (!res)
return -ENOMEM;
res->cmd = *cmd;
memcpy(res->cmd.data, cmd->data, len);
res->cmd_len = struct_size(cmd, data, len);
list_for_each_entry(tmp, &priv->calib_results, list) {
if (tmp->cmd.hdr.op_code == res->cmd.hdr.op_code) {
list_replace(&tmp->list, &res->list);
kfree(tmp);
return 0;
}
}
/* wasn't in list already */
list_add_tail(&res->list, &priv->calib_results);
return 0;
}
void iwl_calib_free_results(struct iwl_priv *priv)
{
struct iwl_calib_result *res, *tmp;
list_for_each_entry_safe(res, tmp, &priv->calib_results, list) {
list_del(&res->list);
kfree(res);
}
}
/*****************************************************************************
* RUNTIME calibrations framework
*****************************************************************************/
/* "false alarms" are signals that our DSP tries to lock onto,
* but then determines that they are either noise, or transmissions
* from a distant wireless network (also "noise", really) that get
* "stepped on" by stronger transmissions within our own network.
* This algorithm attempts to set a sensitivity level that is high
* enough to receive all of our own network traffic, but not so
* high that our DSP gets too busy trying to lock onto non-network
* activity/noise. */
static int iwl_sens_energy_cck(struct iwl_priv *priv,
u32 norm_fa,
u32 rx_enable_time,
struct statistics_general_data *rx_info)
{
u32 max_nrg_cck = 0;
int i = 0;
u8 max_silence_rssi = 0;
u32 silence_ref = 0;
u8 silence_rssi_a = 0;
u8 silence_rssi_b = 0;
u8 silence_rssi_c = 0;
u32 val;
/* "false_alarms" values below are cross-multiplications to assess the
* numbers of false alarms within the measured period of actual Rx
* (Rx is off when we're txing), vs the min/max expected false alarms
* (some should be expected if rx is sensitive enough) in a
* hypothetical listening period of 200 time units (TU), 204.8 msec:
*
* MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time
*
* */
u32 false_alarms = norm_fa * 200 * 1024;
u32 max_false_alarms = MAX_FA_CCK * rx_enable_time;
u32 min_false_alarms = MIN_FA_CCK * rx_enable_time;
struct iwl_sensitivity_data *data = NULL;
const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens;
data = &(priv->sensitivity_data);
data->nrg_auto_corr_silence_diff = 0;
/* Find max silence rssi among all 3 receivers.
* This is background noise, which may include transmissions from other
* networks, measured during silence before our network's beacon */
silence_rssi_a = (u8)((rx_info->beacon_silence_rssi_a &
ALL_BAND_FILTER) >> 8);
silence_rssi_b = (u8)((rx_info->beacon_silence_rssi_b &
ALL_BAND_FILTER) >> 8);
silence_rssi_c = (u8)((rx_info->beacon_silence_rssi_c &
ALL_BAND_FILTER) >> 8);
val = max(silence_rssi_b, silence_rssi_c);
max_silence_rssi = max(silence_rssi_a, (u8) val);
/* Store silence rssi in 20-beacon history table */
data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi;
data->nrg_silence_idx++;
if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L)
data->nrg_silence_idx = 0;
/* Find max silence rssi across 20 beacon history */
for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) {
val = data->nrg_silence_rssi[i];
silence_ref = max(silence_ref, val);
}
IWL_DEBUG_CALIB(priv, "silence a %u, b %u, c %u, 20-bcn max %u\n",
silence_rssi_a, silence_rssi_b