/*
|
* Copyright (C) 2017 Spreadtrum Communications Inc.
|
*
|
* Filename : 11h.c
|
* Abstract : This file is a general implement of 802.11h
|
*
|
* Authors :
|
* Jay.Yang <Jay.Yang@spreadtrum.com>
|
*
|
* This software is licensed under the terms of the GNU General Public
|
* License version 2, as published by the Free Software Foundation, and
|
* may be copied, distributed, and modified under those terms.
|
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*/
|
|
#include "11h.h"
|
#include "work.h"
|
|
int sprdwl_init_dfs_master(struct sprdwl_vif *vif)
|
{
|
vif->dfs_cac_workqueue = alloc_workqueue("SPRDWL_DFS_CAC%s",
|
WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 1, vif->name);
|
if (!vif->dfs_cac_workqueue) {
|
wl_err("alloc DFS CAC workqueue failure\n");
|
vif->ndev = NULL;
|
memset(&vif->wdev, 0, sizeof(vif->wdev));
|
return -ENOMEM;
|
}
|
|
INIT_DELAYED_WORK(&vif->dfs_cac_work, sprdwl_dfs_cac_work_queue);
|
vif->dfs_chan_sw_workqueue = alloc_workqueue("SPRDWL_DFS_CHSW%s",
|
WQ_HIGHPRI | WQ_UNBOUND | WQ_MEM_RECLAIM, 1, vif->name);
|
if (!vif->dfs_chan_sw_workqueue) {
|
wl_err("alloc DFS CHSW workqueue failure\n");
|
vif->ndev = NULL;
|
memset(&vif->wdev, 0, sizeof(vif->wdev));
|
return -ENOMEM;
|
}
|
|
INIT_DELAYED_WORK(&vif->dfs_chan_sw_work,
|
sprdwl_dfs_chan_sw_work_queue);
|
|
return 0;
|
}
|
|
void sprdwl_deinit_dfs_master(struct sprdwl_vif *vif)
|
{
|
if (vif->dfs_cac_workqueue) {
|
flush_workqueue(vif->dfs_cac_workqueue);
|
destroy_workqueue(vif->dfs_cac_workqueue);
|
vif->dfs_cac_workqueue = NULL;
|
}
|
|
if (vif->dfs_chan_sw_workqueue) {
|
flush_workqueue(vif->dfs_chan_sw_workqueue);
|
destroy_workqueue(vif->dfs_chan_sw_workqueue);
|
vif->dfs_chan_sw_workqueue = NULL;
|
}
|
}
|
|
/* This is work queue function for channel switch handling.
|
* This function takes care of updating new channel definitin to
|
* bss config structure, restart AP and indicate channel switch success
|
* to cfg80211.
|
*/
|
void sprdwl_dfs_chan_sw_work_queue(struct work_struct *work)
|
{
|
struct delayed_work *delayed_work =
|
container_of(work, struct delayed_work, work);
|
struct sprdwl_vif *vif =
|
container_of(delayed_work, struct sprdwl_vif,
|
dfs_chan_sw_work);
|
|
cfg80211_ch_switch_notify(vif->ndev, &vif->dfs_chandef);
|
}
|
|
/*This is delayed work emits CAC finished event for cfg80211 if
|
* CAC was started earlier.
|
*/
|
void sprdwl_dfs_cac_work_queue(struct work_struct *work)
|
{
|
struct cfg80211_chan_def chandef;
|
struct delayed_work *delayed_work =
|
container_of(work, struct delayed_work, work);
|
struct sprdwl_vif *vif =
|
container_of(delayed_work, struct sprdwl_vif,
|
dfs_cac_work);
|
|
chandef = vif->dfs_chandef;
|
if (vif->wdev.cac_started) {
|
wl_err("CAC timer finished; No radar detected\n");
|
cfg80211_cac_event(vif->ndev, &chandef,
|
NL80211_RADAR_CAC_FINISHED,
|
GFP_KERNEL);
|
}
|
}
|
|
int sprdwl_cfg80211_start_radar_detection(struct wiphy *wiphy,
|
struct net_device *ndev,
|
struct cfg80211_chan_def *chandef,
|
u32 cac_time_ms)
|
{
|
struct sprdwl_vif *vif = netdev_priv(ndev);
|
struct sprdwl_radar_params radar_params;
|
|
wl_debug("%s enter:\n", __func__);
|
radar_params.chan_num = chandef->chan->hw_value;
|
radar_params.chan_width = chandef->width;
|
radar_params.cac_time_ms = cac_time_ms;
|
|
memcpy(&vif->dfs_chandef, chandef, sizeof(vif->dfs_chandef));
|
/*send radar detect CMD*/
|
sprdwl_send_dfs_cmd(vif, &radar_params, sizeof(radar_params));
|
|
queue_delayed_work(vif->dfs_cac_workqueue, &vif->dfs_cac_work,
|
msecs_to_jiffies(cac_time_ms));
|
|
return 0;
|
}
|
|
int sprdwl_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *ndev,
|
struct cfg80211_csa_settings *params)
|
{
|
struct sprdwl_vif *vif = netdev_priv(ndev);
|
struct ieee_types_header *chsw_ie;
|
struct ieee80211_channel_sw_ie *channel_sw;
|
struct cfg80211_beacon_data *beacon = ¶ms->beacon_csa;
|
int chsw_msec = 0;
|
|
if (vif->wdev.cac_started)
|
return -EBUSY;
|
|
if (cfg80211_chandef_identical(¶ms->chandef, &vif->dfs_chandef))
|
return -EINVAL;
|
|
chsw_ie = (void *)cfg80211_find_ie(WLAN_EID_CHANNEL_SWITCH,
|
params->beacon_csa.tail, params->beacon_csa.tail_len);
|
|
if (!chsw_ie) {
|
wl_err("Couldn't parse chan switch announcement IE\n");
|
return -EINVAL;
|
}
|
|
channel_sw = (void *)(chsw_ie + 1);
|
if (channel_sw->mode) {
|
if (netif_carrier_ok(vif->ndev))
|
netif_carrier_off(vif->ndev);
|
/*stop tx Q*/
|
if (!netif_queue_stopped(vif->ndev))
|
netif_stop_queue(vif->ndev);
|
}
|
|
if (beacon->tail_len)
|
sprdwl_reset_beacon(vif->priv, vif->ctx_id,
|
beacon->tail, beacon->tail_len);
|
|
/*set mgmt ies*/
|
if (sprdwl_change_beacon(vif, beacon)) {
|
wl_err("set beacon IE failure\n");
|
return -EINVAL;
|
}
|
|
memcpy(&vif->dfs_chandef, ¶ms->chandef, sizeof(vif->dfs_chandef));
|
chsw_msec = max(channel_sw->count * vif->priv->beacon_period, 500);
|
queue_delayed_work(vif->dfs_chan_sw_workqueue, &vif->dfs_chan_sw_work,
|
msecs_to_jiffies(chsw_msec));
|
return 0;
|
}
|
|
void sprdwl_stop_radar_detection(struct sprdwl_vif *vif,
|
struct cfg80211_chan_def *chandef)
|
{
|
struct sprdwl_work *misc_work;
|
struct sprdwl_radar_params radar_params;
|
|
memset(&radar_params, 0, sizeof(struct sprdwl_radar_params));
|
radar_params.chan_num = chandef->chan->hw_value;
|
radar_params.chan_width = chandef->width;
|
radar_params.cac_time_ms = 0;
|
|
/*send stop radar detection cmd*/
|
misc_work = sprdwl_alloc_work(sizeof(radar_params));
|
misc_work->vif = vif;
|
misc_work->id = SPRDWL_WORK_DFS;
|
memcpy(misc_work->data, &radar_params, sizeof(radar_params));
|
|
sprdwl_queue_work(vif->priv, misc_work);
|
}
|
|
/* This function is to abort ongoing CAC upon stopping AP operations
|
* or during unload.
|
*/
|
void sprdwl_abort_cac(struct sprdwl_vif *vif)
|
{
|
if (vif->wdev.cac_started) {
|
sprdwl_stop_radar_detection(vif, &vif->dfs_chandef);
|
cancel_delayed_work_sync(&vif->dfs_cac_work);
|
cfg80211_cac_event(vif->ndev, &vif->dfs_chandef,
|
NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
|
}
|
}
|
|
/* Handler for radar detected event from FW.*/
|
int sprdwl_11h_handle_radar_detected(struct sprdwl_vif *vif,
|
u8 *data, u16 len)
|
{
|
struct sprdwl_radar_event *rdr_event;
|
|
rdr_event = (struct sprdwl_radar_event *)data;
|
|
wl_debug("radar detected; indicating kernel\n");
|
sprdwl_stop_radar_detection(vif, &vif->dfs_chandef);
|
cfg80211_radar_event(vif->priv->wiphy, &vif->dfs_chandef,
|
GFP_KERNEL);
|
/*print radar detect reg & type*/
|
wl_debug("regdomain:%d,radar detection type:%d\n",
|
rdr_event->reg_domain, rdr_event->det_type);
|
return 0;
|
}
|
|
void sprdwl_send_dfs_cmd(struct sprdwl_vif *vif, void *data, int len)
|
{
|
struct sprdwl_msg_buf *msg;
|
|
wl_debug("%s:enter\n", __func__);
|
msg = sprdwl_cmd_getbuf(vif->priv, len, vif->ctx_id,
|
SPRDWL_HEAD_RSP, WIFI_CMD_RADAR_DETECT);
|
memcpy(msg->data, data, len);
|
sprdwl_cmd_send_recv(vif->priv, msg, CMD_WAIT_TIMEOUT, NULL, NULL);
|
}
|