2025-05-10 21:58:58 +08:00

968 lines
27 KiB
C

/*
* Copyright (C) 2015 Spreadtrum Communications Inc.
*
* Abstract : This file is an implementation for cfg80211 subsystem
*
* Authors:
* Chaojie Xu <chaojie.xu@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 <linux/etherdevice.h>
#include <net/cfg80211.h>
#include <net/netlink.h>
#include "sprdwl.h"
#include "rtt.h"
/* FTM session ID we use with FW */
#define FTM_ESSION_ID 1
/* fixed spare allocation we reserve in NL messages we allocate */
#define FTM_NL_EXTRA_ALLOC 32
/* approx maximum length for FTM_MEAS_RESULT NL80211 event */
#define FTM_MEAS_RESULT_MAX_LENGTH 2048
/* maximum number of allowed FTM measurements per burst */
#define FTM_MAX_MEAS_PER_BURST 31
/* initial token to use on non-secure FTM measurement */
#define FTM_DEFAULT_INITIAL_TOKEN 2
#define FTM_MAX_LCI_LENGTH (240)
#define FTM_MAX_LCR_LENGTH (240)
/* max rtt cmd response length */
#define RTT_RSP_LEN (128)
enum session_start_flags {
FTM_SESSION_START_FLAG_SECURED = 0x1,
FTM_SESSION_START_FLAG_ASAP = 0x2,
FTM_SESSION_START_FLAG_LCI_REQ = 0x4,
FTM_SESSION_START_FLAG_LCR_REQ = 0x8,
};
enum rtt_subcmd {
RTT_ENABLE,
RTT_DISABLE,
RTT_GET_CAPABILITIES,
RTT_RANGE_REQUEST,
RTT_RANGE_CANCEL,
RTT_SET_CLI,
RTT_SET_CLR,
RTT_GET_RESPONDER_INFO,
RTT_ENABLE_RESPONDER,
RTT_DISABLE_RESPONDER,
};
enum rtt_subevt {
RTT_SESSION_END,
RTT_PER_DEST_RES,
};
/* Responder FTM Results */
struct sprdwl_responder_ftm_res {
u8 t1[6];
u8 t2[6];
u8 t3[6];
u8 t4[6];
__le16 tod_err;
__le16 toa_err;
__le16 tod_err_initiator;
__le16 toa_err_initiator;
} __packed;
enum ftm_per_dest_res_status {
FTM_PER_DEST_RES_NO_ERROR = 0x00,
FTM_PER_DEST_RES_TX_RX_FAIL = 0x01,
FTM_PER_DEST_RES_PARAM_DONT_MATCH = 0x02,
};
enum ftm_per_dest_res_flags {
FTM_PER_DEST_RES_REQ_START = 0x01,
FTM_PER_DEST_RES_BURST_REPORT_END = 0x02,
FTM_PER_DEST_RES_REQ_END = 0x04,
FTM_PER_DEST_RES_PARAM_UPDATE = 0x08,
};
struct ftm_per_dest_res {
/* FTM session ID */
__le32 session_id;
/* destination MAC address */
u8 dst_mac[ETH_ALEN];
/* wmi_tof_ftm_per_dest_res_flags_e */
u8 flags;
/* wmi_tof_ftm_per_dest_res_status_e */
u8 status;
/* responder ASAP */
u8 responder_asap;
/* responder number of FTM per burst */
u8 responder_num_ftm_per_burst;
/* responder number of FTM burst exponent */
u8 responder_num_ftm_bursts_exp;
/* responder burst duration ,wmi_tof_burst_duration_e */
u8 responder_burst_duration;
/* responder burst period, indicate interval between two consecutive
* burst instances, in units of 100 ms
*/
__le16 responder_burst_period;
/* receive burst counter */
__le16 bursts_cnt;
/* tsf of responder start burst */
__le32 tsf_sync;
/* actual received ftm per burst */
u8 actual_ftm_per_burst;
u8 reserved0[7];
struct sprdwl_responder_ftm_res responder_ftm_res[0];
} __packed;
struct ftm_dest_info {
u8 channel;
u8 flags;
u8 initial_token;
u8 num_of_ftm_per_burst;
u8 num_of_bursts_exp;
u8 burst_duration;
/* Burst Period indicate interval between two consecutive burst
* instances, in units of 100 ms
*/
__le16 burst_period;
u8 dst_mac[ETH_ALEN];
__le16 reserved;
} __packed;
struct ftm_session_start {
__le32 session_id;
u8 num_of_aoa_measures;
u8 aoa_type;
__le16 num_of_dest;
u8 reserved[4];
struct ftm_dest_info dest_info[0];
} __packed;
struct sprdwl_cmd_rtt {
u8 sub_cmd;
__le16 len;
u8 data[0];
} __packed;
static const struct
nla_policy sprdwl_nl80211_loc_policy[SPRDWL_VENDOR_ATTR_LOC_MAX + 1] = {
[SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE] = { .type = NLA_U64 },
[SPRDWL_VENDOR_ATTR_LOC_CAPA] = { .type = NLA_NESTED },
[SPRDWL_VENDOR_ATTR_FTM_MEAS_PEERS] = { .type = NLA_NESTED },
[SPRDWL_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS] = { .type = NLA_NESTED },
[SPRDWL_VENDOR_ATTR_FTM_RESPONDER_ENABLE] = { .type = NLA_FLAG },
[SPRDWL_VENDOR_ATTR_LOC_SESSION_STATUS] = { .type = NLA_U32 },
[SPRDWL_VENDOR_ATTR_FTM_INITIAL_TOKEN] = { .type = NLA_U8 },
[SPRDWL_VENDOR_ATTR_AOA_TYPE] = { .type = NLA_U32 },
[SPRDWL_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK] = { .type = NLA_U32 },
[SPRDWL_VENDOR_ATTR_FREQ] = { .type = NLA_U32 },
};
static const struct
nla_policy sprdwl_nl80211_ftm_peer_policy[
SPRDWL_VENDOR_ATTR_FTM_PEER_MAX + 1] = {
[SPRDWL_VENDOR_ATTR_FTM_PEER_MAC_ADDR] = { .len = ETH_ALEN },
[SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS] = { .type = NLA_U32 },
[SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS] = { .type = NLA_NESTED },
[SPRDWL_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID] = { .type = NLA_U8 },
[SPRDWL_VENDOR_ATTR_FTM_PEER_FREQ] = { .type = NLA_U32 },
};
static const struct
nla_policy sprdwl_nl80211_ftm_meas_param_policy[
SPRDWL_VENDOR_ATTR_FTM_PARAM_MAX + 1] = {
[SPRDWL_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST] = { .type = NLA_U8 },
[SPRDWL_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP] = { .type = NLA_U8 },
[SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_DURATION] = { .type = NLA_U8 },
[SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD] = { .type = NLA_U16 },
};
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
static u8 sprdwl_ftm_get_channel(struct wiphy *wiphy,
const u8 *mac_addr, u32 freq)
{
struct cfg80211_bss *bss;
struct ieee80211_channel *chan;
u8 channel;
if (freq) {
chan = ieee80211_get_channel(wiphy, freq);
if (!chan) {
wl_err("invalid freq: %d\n", freq);
return 0;
}
channel = chan->hw_value;
} else {
bss = cfg80211_get_bss(wiphy, NULL, mac_addr,
NULL, 0, WLAN_CAPABILITY_ESS,
WLAN_CAPABILITY_ESS);
if (!bss) {
wl_err("Unable to find BSS\n");
return 0;
}
channel = bss->channel->hw_value;
cfg80211_put_bss(wiphy, bss);
}
wl_info("target %pM at channel %d\n", mac_addr, channel);
return channel;
}
static int sprdwl_ftm_parse_meas_params(struct sprdwl_vif *vif,
struct nlattr *attr,
struct sprdwl_ftm_meas_params *params)
{
struct nlattr *tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_MAX + 1];
int rc;
if (!attr) {
/* temporary defaults for one-shot measurement */
params->meas_per_burst = 1;
params->burst_period = 5; /* 500 milliseconds */
return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
rc = nla_parse_nested(tb, SPRDWL_VENDOR_ATTR_FTM_PARAM_MAX,
attr, sprdwl_nl80211_ftm_meas_param_policy, NULL);
#else
rc = nla_parse_nested(tb, SPRDWL_VENDOR_ATTR_FTM_PARAM_MAX,
attr, sprdwl_nl80211_ftm_meas_param_policy);
#endif
if (rc) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: invalid measurement params\n", __func__);
return rc;
}
if (tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST])
params->meas_per_burst = nla_get_u8(
tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST]);
if (tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP])
params->num_of_bursts_exp = nla_get_u8(
tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP]);
if (tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_DURATION])
params->burst_duration = nla_get_u8(
tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_DURATION]);
if (tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD])
params->burst_period = nla_get_u16(
tb[SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD]);
return 0;
}
static int
sprdwl_ftm_validate_meas_params(struct sprdwl_vif *vif,
struct sprdwl_ftm_meas_params *params)
{
if (params->meas_per_burst > FTM_MAX_MEAS_PER_BURST ||
params->num_of_bursts_exp != 0) {
wl_ndev_log(L_ERR, vif->ndev, "%s: invalid meas per burst\n", __func__);
return -EINVAL;
}
return 0;
}
static int sprdwl_ftm_append_meas_params(struct sprdwl_priv *priv,
struct sk_buff *msg,
struct sprdwl_ftm_meas_params *params)
{
struct nlattr *nl_p;
nl_p = nla_nest_start(
msg, SPRDWL_VENDOR_ATTR_FTM_PEER_RES_MEAS_PARAMS);
if (!nl_p)
goto out_put_failure;
if (nla_put_u8(msg, SPRDWL_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST,
params->meas_per_burst) ||
nla_put_u8(msg, SPRDWL_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP,
params->num_of_bursts_exp) ||
nla_put_u8(msg, SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_DURATION,
params->burst_duration) ||
nla_put_u16(msg, SPRDWL_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD,
params->burst_period))
goto out_put_failure;
nla_nest_end(msg, nl_p);
return 0;
out_put_failure:
return -ENOBUFS;
}
static int sprdwl_ftm_append_peer_meas_res(struct sprdwl_priv *priv,
struct sk_buff *msg,
struct sprdwl_ftm_peer_meas_res *res)
{
struct nlattr *nl_mres, *nl_f;
int i;
if (nla_put(msg, SPRDWL_VENDOR_ATTR_FTM_PEER_RES_MAC_ADDR,
ETH_ALEN, res->mac_addr) ||
nla_put_u32(msg, SPRDWL_VENDOR_ATTR_FTM_PEER_RES_FLAGS,
res->flags) ||
nla_put_u8(msg, SPRDWL_VENDOR_ATTR_FTM_PEER_RES_STATUS,
res->status))
goto out_put_failure;
if (res->status == SPRDWL_VENDOR_ATTR_FTM_PEER_RES_STATUS_FAILED &&
nla_put_u8(msg,
SPRDWL_VENDOR_ATTR_FTM_PEER_RES_VALUE_SECONDS,
res->value_seconds))
goto out_put_failure;
if (res->has_params &&
sprdwl_ftm_append_meas_params(priv, msg, &res->params))
goto out_put_failure;
nl_mres = nla_nest_start(msg, SPRDWL_VENDOR_ATTR_FTM_PEER_RES_MEAS);
if (!nl_mres)
goto out_put_failure;
for (i = 0; i < res->n_meas; i++) {
nl_f = nla_nest_start(msg, i);
if (!nl_f)
goto out_put_failure;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
if (nla_put_u64_64bit(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T1,
res->meas[i].t1, 0) ||
nla_put_u64_64bit(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T2,
res->meas[i].t2, 0) ||
nla_put_u64_64bit(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T3,
res->meas[i].t3, 0) ||
nla_put_u64_64bit(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T4,
res->meas[i].t4, 0))
goto out_put_failure;
#else
if (nla_put_u64(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T1,
res->meas[i].t1) ||
nla_put_u64(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T2,
res->meas[i].t2) ||
nla_put_u64(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T3,
res->meas[i].t3) ||
nla_put_u64(msg, SPRDWL_VENDOR_ATTR_FTM_MEAS_T4,
res->meas[i].t4))
goto out_put_failure;
#endif
nla_nest_end(msg, nl_f);
}
nla_nest_end(msg, nl_mres);
return 0;
out_put_failure:
wl_err("%s: fail to append peer result\n", __func__);
return -ENOBUFS;
}
static void sprdwl_ftm_send_meas_result(struct sprdwl_priv *priv,
struct sprdwl_ftm_peer_meas_res *res)
{
struct sk_buff *skb = NULL;
struct nlattr *nl_res;
int rc = 0;
wl_info("sending %d results for peer %pM\n",
res->n_meas, res->mac_addr);
skb = cfg80211_vendor_event_alloc(
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 83)
priv->wiphy, NULL,
#else
priv->wiphy,
#endif
FTM_MEAS_RESULT_MAX_LENGTH,
SPRD_VENDOR_EVENT_FTM_MEAS_RESULT_INDEX,
GFP_KERNEL);
if (!skb) {
wl_err("fail to allocate measurement result\n");
rc = -ENOMEM;
goto out;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
if (nla_put_u64_64bit(
skb, SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE,
priv->ftm.session_cookie, 0)) {
#else
if (nla_put_u64(
skb, SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE,
priv->ftm.session_cookie)) {
#endif
rc = -ENOBUFS;
goto out;
}
nl_res = nla_nest_start(skb,
SPRDWL_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS);
if (!nl_res) {
rc = -ENOBUFS;
goto out;
}
rc = sprdwl_ftm_append_peer_meas_res(priv, skb, res);
if (rc)
goto out;
nla_nest_end(skb, nl_res);
cfg80211_vendor_event(skb, GFP_KERNEL);
skb = NULL;
out:
if (skb)
kfree_skb(skb);
if (rc)
wl_err("send peer result failed, err %d\n", rc);
}
static void sprdwl_ftm_send_peer_res(struct sprdwl_priv *priv)
{
if (!priv->ftm.has_ftm_res || !priv->ftm.ftm_res)
return;
sprdwl_ftm_send_meas_result(priv, priv->ftm.ftm_res);
priv->ftm.has_ftm_res = 0;
priv->ftm.ftm_res->n_meas = 0;
}
static int
sprdwl_ftm_cfg80211_start_session(struct sprdwl_priv *priv,
struct sprdwl_vif *vif,
struct sprdwl_ftm_session_request *request)
{
int ret = 0;
bool has_lci = false, has_lcr = false;
u8 max_meas = 0, channel, *ptr;
u32 i, cmd_len;
struct ftm_session_start *cmd;
struct sprdwl_msg_buf *msg;
struct sprdwl_cmd_rtt *rtt;
mutex_lock(&priv->ftm.lock);
if (priv->ftm.session_started) {
wl_err("%s: FTM session already running\n", __func__);
ret = -EALREADY;
goto out;
}
for (i = 0; i < request->n_peers; i++) {
if (request->peers[i].flags &
SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI)
has_lci = true;
if (request->peers[i].flags &
SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR)
has_lcr = true;
max_meas = max(max_meas,
request->peers[i].params.meas_per_burst);
}
priv->ftm.ftm_res = kzalloc(sizeof(*priv->ftm.ftm_res) +
max_meas * sizeof(struct sprdwl_ftm_peer_meas) +
(has_lci ? FTM_MAX_LCI_LENGTH : 0) +
(has_lcr ? FTM_MAX_LCR_LENGTH : 0), GFP_KERNEL);
if (!priv->ftm.ftm_res) {
ret = -ENOMEM;
goto out;
}
ptr = (u8 *)priv->ftm.ftm_res;
ptr += sizeof(struct sprdwl_ftm_peer_meas_res) +
max_meas * sizeof(struct sprdwl_ftm_peer_meas);
if (has_lci) {
priv->ftm.ftm_res->lci = ptr;
ptr += FTM_MAX_LCI_LENGTH;
}
if (has_lcr)
priv->ftm.ftm_res->lcr = ptr;
priv->ftm.max_ftm_meas = max_meas;
cmd_len = sizeof(struct ftm_session_start) +
request->n_peers * sizeof(struct ftm_dest_info);
cmd = kzalloc(cmd_len, GFP_KERNEL);
if (!cmd) {
ret = -ENOMEM;
goto out_ftm_res;
}
cmd->session_id = cpu_to_le32(FTM_ESSION_ID);
cmd->num_of_dest = cpu_to_le16(request->n_peers);
for (i = 0; i < request->n_peers; i++) {
ether_addr_copy(cmd->dest_info[i].dst_mac,
request->peers[i].mac_addr);
channel = sprdwl_ftm_get_channel(priv->wiphy,
request->peers[i].mac_addr,
request->peers[i].freq);
if (!channel) {
wl_err("%s: can't find FTM target at index %d\n",
__func__, i);
ret = -EINVAL;
goto out_cmd;
}
cmd->dest_info[i].channel = channel - 1;
if (request->peers[i].flags &
SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_SECURE) {
cmd->dest_info[i].flags |=
FTM_SESSION_START_FLAG_SECURED;
cmd->dest_info[i].initial_token =
request->peers[i].secure_token_id;
} else {
cmd->dest_info[i].initial_token =
FTM_DEFAULT_INITIAL_TOKEN;
}
if (request->peers[i].flags &
SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_ASAP)
cmd->dest_info[i].flags |=
FTM_SESSION_START_FLAG_ASAP;
if (request->peers[i].flags &
SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI)
cmd->dest_info[i].flags |=
FTM_SESSION_START_FLAG_LCI_REQ;
if (request->peers[i].flags &
SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR)
cmd->dest_info[i].flags |=
FTM_SESSION_START_FLAG_LCR_REQ;
cmd->dest_info[i].num_of_ftm_per_burst =
request->peers[i].params.meas_per_burst;
cmd->dest_info[i].num_of_bursts_exp =
request->peers[i].params.num_of_bursts_exp;
cmd->dest_info[i].burst_duration =
request->peers[i].params.burst_duration;
cmd->dest_info[i].burst_period =
cpu_to_le16(request->peers[i].params.burst_period);
}
/* send range request data to the FW */
msg = sprdwl_cmd_getbuf(priv, sizeof(struct sprdwl_cmd_rtt) + cmd_len,
vif->ctx_id, SPRDWL_HEAD_RSP, WIFI_CMD_RTT);
if (!msg) {
ret = -ENOMEM;
goto out_cmd;
}
rtt = (struct sprdwl_cmd_rtt *)msg->data;
rtt->sub_cmd = RTT_GET_CAPABILITIES;
rtt->len = cmd_len;
memcpy(rtt->data, cmd, cmd_len);
ret = sprdwl_cmd_send_recv(priv, msg,
CMD_WAIT_TIMEOUT, NULL, 0);
if (ret) {
wl_ndev_log(L_ERR, vif->ndev, "%s: ret=%d\n", __func__, ret);
} else {
priv->ftm.session_cookie = request->session_cookie;
priv->ftm.session_started = 1;
}
out_cmd:
kfree(cmd);
out_ftm_res:
if (ret) {
kfree(priv->ftm.ftm_res);
priv->ftm.ftm_res = NULL;
}
out:
mutex_unlock(&priv->ftm.lock);
return ret;
}
static void
sprdwl_ftm_session_ended(struct sprdwl_priv *priv, u32 status)
{
struct sk_buff *skb = NULL;
mutex_lock(&priv->ftm.lock);
if (!priv->ftm.session_started) {
wl_err("%s: FTM session not started, ignoring\n", __func__);
return;
}
wl_info("%s: finishing FTM session\n", __func__);
/* send left-over results if any */
sprdwl_ftm_send_peer_res(priv);
priv->ftm.session_started = 0;
kfree(priv->ftm.ftm_res);
priv->ftm.ftm_res = NULL;
skb = cfg80211_vendor_event_alloc(
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 83)
priv->wiphy, NULL,
#else
priv->wiphy,
#endif
FTM_NL_EXTRA_ALLOC,
SPRD_VENDOR_EVENT_FTM_SESSION_DONE_INDEX,
GFP_KERNEL);
if (!skb)
goto out;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
if (nla_put_u64_64bit(skb,
SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE,
priv->ftm.session_cookie, 0) ||
nla_put_u32(skb,
SPRDWL_VENDOR_ATTR_LOC_SESSION_STATUS, status)) {
#else
if (nla_put_u64(skb,
SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE,
priv->ftm.session_cookie) ||
nla_put_u32(skb,
SPRDWL_VENDOR_ATTR_LOC_SESSION_STATUS, status)) {
#endif
wl_err("%s: failed to fill session done event\n", __func__);
goto out;
}
cfg80211_vendor_event(skb, GFP_KERNEL);
skb = NULL;
out:
kfree_skb(skb);
mutex_unlock(&priv->ftm.lock);
}
void sprdwl_ftm_event_per_dest_res(struct sprdwl_priv *priv,
struct ftm_per_dest_res *res)
{
u32 i, index;
__le64 tmp = 0;
u8 n_meas;
mutex_lock(&priv->ftm.lock);
if (!priv->ftm.session_started || !priv->ftm.ftm_res) {
wl_err("%s: Session not running, ignoring res event\n",
__func__);
goto out;
}
if (priv->ftm.has_ftm_res &&
!ether_addr_equal(res->dst_mac, priv->ftm.ftm_res->mac_addr)) {
wl_err("%s: previous peer not properly terminated\n",
__func__);
sprdwl_ftm_send_peer_res(priv);
}
if (!priv->ftm.has_ftm_res) {
ether_addr_copy(priv->ftm.ftm_res->mac_addr, res->dst_mac);
priv->ftm.has_ftm_res = 1;
}
n_meas = res->actual_ftm_per_burst;
switch (res->status) {
case FTM_PER_DEST_RES_NO_ERROR:
priv->ftm.ftm_res->status =
SPRDWL_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK;
break;
case FTM_PER_DEST_RES_TX_RX_FAIL:
/* FW reports corrupted results here, discard. */
n_meas = 0;
priv->ftm.ftm_res->status =
SPRDWL_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK;
break;
case FTM_PER_DEST_RES_PARAM_DONT_MATCH:
priv->ftm.ftm_res->status =
SPRDWL_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID;
break;
default:
wl_err("%s: unexpected status %d\n", __func__, res->status);
priv->ftm.ftm_res->status =
SPRDWL_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID;
break;
}
for (i = 0; i < n_meas; i++) {
index = priv->ftm.ftm_res->n_meas;
if (index >= priv->ftm.max_ftm_meas) {
wl_info("%s: Too many measurements\n", __func__);
break;
}
memcpy(&tmp, res->responder_ftm_res[i].t1,
sizeof(res->responder_ftm_res[i].t1));
priv->ftm.ftm_res->meas[index].t1 = le64_to_cpu(tmp);
memcpy(&tmp, res->responder_ftm_res[i].t2,
sizeof(res->responder_ftm_res[i].t2));
priv->ftm.ftm_res->meas[index].t2 = le64_to_cpu(tmp);
memcpy(&tmp, res->responder_ftm_res[i].t3,
sizeof(res->responder_ftm_res[i].t3));
priv->ftm.ftm_res->meas[index].t3 = le64_to_cpu(tmp);
memcpy(&tmp, res->responder_ftm_res[i].t4,
sizeof(res->responder_ftm_res[i].t4));
priv->ftm.ftm_res->meas[index].t4 = le64_to_cpu(tmp);
priv->ftm.ftm_res->n_meas++;
}
if (res->flags & FTM_PER_DEST_RES_BURST_REPORT_END)
sprdwl_ftm_send_peer_res(priv);
out:
mutex_unlock(&priv->ftm.lock);
}
int sprdwl_event_ftm(struct sprdwl_vif *vif, u8 *data, u16 len)
{
struct sprdwl_priv *priv = vif->priv;
u8 sub_event;
u32 status;
struct ftm_per_dest_res *res;
memcpy(&sub_event, data, sizeof(sub_event));
data += sizeof(sub_event);
len -= sizeof(sub_event);
switch (sub_event) {
case RTT_SESSION_END:
memcpy(&status, data, sizeof(status));
sprdwl_ftm_session_ended(priv, status);
break;
case RTT_PER_DEST_RES:
res = (struct ftm_per_dest_res *)data;
sprdwl_ftm_event_per_dest_res(priv, res);
break;
default:
wl_ndev_log(L_ERR, vif->ndev, "%s: unknown FTM event\n", __func__);
break;
}
return 0;
}
int sprdwl_ftm_get_capabilities(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int len)
{
struct sprdwl_msg_buf *msg;
struct sprdwl_cmd_rtt *cmd;
struct sprdwl_vif *vif = netdev_priv(wdev->netdev);
u8 rsp[RTT_RSP_LEN] = {0x0};
u16 rsp_len = RTT_RSP_LEN;
int ret = 0;
struct sk_buff *skb;
struct nlattr *attr;
/* get the capabilities from the FW */
msg = sprdwl_cmd_getbuf(vif->priv, sizeof(struct sprdwl_cmd_rtt) + len,
vif->ctx_id, SPRDWL_HEAD_RSP, WIFI_CMD_RTT);
if (!msg)
return -ENOMEM;
cmd = (struct sprdwl_cmd_rtt *)msg->data;
cmd->sub_cmd = RTT_GET_CAPABILITIES;
cmd->len = len;
memcpy(cmd->data, data, len);
ret = sprdwl_cmd_send_recv(vif->priv, msg,
CMD_WAIT_TIMEOUT, rsp, &rsp_len);
if (ret) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: ret=%d, rsp_len=%d\n", __func__, ret, rsp_len);
}
/* report capabilities */
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, RTT_RSP_LEN);
if (!skb)
return -ENOMEM;
attr = nla_nest_start(skb, SPRDWL_VENDOR_ATTR_LOC_CAPA);
if (!attr ||
nla_put_u32(skb, SPRDWL_VENDOR_ATTR_LOC_CAPA_FLAGS,
SPRDWL_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_RESPONDER |
SPRDWL_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_INITIATOR |
SPRDWL_VENDOR_ATTR_LOC_CAPA_FLAG_ASAP |
SPRDWL_VENDOR_ATTR_LOC_CAPA_FLAG_AOA) ||
nla_put_u16(skb, SPRDWL_VENDOR_ATTR_FTM_CAPA_MAX_NUM_SESSIONS,
1) ||
nla_put_u16(skb, SPRDWL_VENDOR_ATTR_FTM_CAPA_MAX_NUM_PEERS, 1) ||
nla_put_u8(skb, SPRDWL_VENDOR_ATTR_FTM_CAPA_MAX_NUM_BURSTS_EXP,
0) ||
nla_put_u8(skb, SPRDWL_VENDOR_ATTR_FTM_CAPA_MAX_MEAS_PER_BURST,
4) ||
nla_put_u32(skb, SPRDWL_VENDOR_ATTR_AOA_CAPA_SUPPORTED_TYPES,
BIT(SPRDWL_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE))) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: fail to fill capabilities\n", __func__);
kfree_skb(skb);
return -ENOMEM;
}
nla_nest_end(skb, attr);
return cfg80211_vendor_cmd_reply(skb);
}
int sprdwl_ftm_start_session(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct sprdwl_priv *priv = wiphy_priv(wiphy);
struct sprdwl_vif *vif = netdev_priv(wdev->netdev);
struct sprdwl_ftm_session_request *request;
struct nlattr *tb[SPRDWL_VENDOR_ATTR_LOC_MAX + 1];
struct nlattr *tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MAX + 1];
struct nlattr *peer;
int rc, n_peers = 0, index = 0, rem;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
rc = nla_parse(tb, SPRDWL_VENDOR_ATTR_LOC_MAX, data, data_len,
sprdwl_nl80211_loc_policy, NULL);
#else
rc = nla_parse(tb, SPRDWL_VENDOR_ATTR_LOC_MAX, data, data_len,
sprdwl_nl80211_loc_policy);
#endif
if (rc) {
wl_ndev_log(L_ERR, vif->ndev, "%s: invalid FTM attribute\n", __func__);
return rc;
}
if (!tb[SPRDWL_VENDOR_ATTR_FTM_MEAS_PEERS]) {
wl_ndev_log(L_ERR, vif->ndev, "%s: no peers specified\n", __func__);
return -EINVAL;
}
if (!tb[SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE]) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: session cookie not specified\n", __func__);
return -EINVAL;
}
nla_for_each_nested(peer, tb[SPRDWL_VENDOR_ATTR_FTM_MEAS_PEERS],
rem)
n_peers++;
if (!n_peers) {
wl_ndev_log(L_ERR, vif->ndev, "%s: empty peer list\n", __func__);
return -EINVAL;
}
/* for now only allow measurement for a single peer */
if (n_peers != 1) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: only single peer allowed\n", __func__);
return -EINVAL;
}
request = kzalloc(sizeof(*request) +
n_peers * sizeof(struct sprdwl_ftm_meas_peer_info),
GFP_KERNEL);
if (!request)
return -ENOMEM;
request->session_cookie =
nla_get_u64(tb[SPRDWL_VENDOR_ATTR_FTM_SESSION_COOKIE]);
request->n_peers = n_peers;
nla_for_each_nested(peer, tb[SPRDWL_VENDOR_ATTR_FTM_MEAS_PEERS],
rem) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
rc = nla_parse_nested(tb2, SPRDWL_VENDOR_ATTR_FTM_PEER_MAX,
peer, sprdwl_nl80211_ftm_peer_policy, NULL);
#else
rc = nla_parse_nested(tb2, SPRDWL_VENDOR_ATTR_FTM_PEER_MAX,
peer, sprdwl_nl80211_ftm_peer_policy);
#endif
if (rc) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: invalid peer attribute\n", __func__);
goto out;
}
if (!tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MAC_ADDR] ||
nla_len(tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MAC_ADDR])
!= ETH_ALEN) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: peer MAC address missing or invalid\n",
__func__);
rc = -EINVAL;
goto out;
}
memcpy(request->peers[index].mac_addr,
nla_data(tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MAC_ADDR]),
ETH_ALEN);
if (tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_FREQ])
request->peers[index].freq = nla_get_u32(
tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_FREQ]);
if (tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS])
request->peers[index].flags = nla_get_u32(
tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS]);
if (tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID])
request->peers[index].secure_token_id = nla_get_u8(
tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID]);
rc = sprdwl_ftm_parse_meas_params(
vif,
tb2[SPRDWL_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS],
&request->peers[index].params);
if (!rc)
rc = sprdwl_ftm_validate_meas_params(
vif, &request->peers[index].params);
if (rc)
goto out;
index++;
}
rc = sprdwl_ftm_cfg80211_start_session(priv, vif, request);
out:
kfree(request);
return rc;
}
int sprdwl_ftm_abort_session(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int len)
{
struct sprdwl_msg_buf *msg;
struct sprdwl_cmd_rtt *cmd;
struct sprdwl_priv *priv = wiphy_priv(wiphy);
struct sprdwl_vif *vif = netdev_priv(wdev->netdev);
int ret;
mutex_lock(&priv->ftm.lock);
if (!priv->ftm.session_started) {
wl_ndev_log(L_ERR, vif->ndev,
"%s: FTM session not started\n", __func__);
return -EAGAIN;
}
/* send cancel range request */
msg = sprdwl_cmd_getbuf(priv, sizeof(struct sprdwl_cmd_rtt) + len,
vif->ctx_id, 0, WIFI_CMD_RTT);
if (!msg)
return -ENOMEM;
cmd = (struct sprdwl_cmd_rtt *)msg->data;
cmd->sub_cmd = RTT_RANGE_CANCEL;
cmd->len = len;
memcpy(cmd->data, data, len);
ret = sprdwl_cmd_send_recv(priv, msg,
CMD_WAIT_TIMEOUT, NULL, 0);
if (ret)
wl_ndev_log(L_ERR, vif->ndev, "%s: ret=%d\n", __func__, ret);
mutex_unlock(&priv->ftm.lock);
return ret;
}
int sprdwl_ftm_get_responder_info(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int len)
{
struct sprdwl_vif *vif = netdev_priv(wdev->netdev);
/* get responder info */
wl_ndev_log(L_INFO, vif->ndev, "%s: not implemented yet\n", __func__);
return -ENOTSUPP;
}
int sprdwl_ftm_configure_responder(struct wiphy *wiphy,
struct wireless_dev *wdev,
const void *data, int data_len)
{
struct sprdwl_vif *vif = netdev_priv(wdev->netdev);
/* enable or disable responder */
wl_ndev_log(L_INFO, vif->ndev, "%s: not implemented yet\n", __func__);
return -ENOTSUPP;
}
void sprdwl_ftm_init(struct sprdwl_priv *priv)
{
mutex_init(&priv->ftm.lock);
}
void sprdwl_ftm_deinit(struct sprdwl_priv *priv)
{
kfree(priv->ftm.ftm_res);
}
void sprdwl_ftm_stop_operations(struct sprdwl_priv *priv)
{
sprdwl_ftm_session_ended(
priv, SPRDWL_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED);
}
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */