summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/ath/wil6210/ftm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/ath/wil6210/ftm.c')
-rw-r--r--drivers/net/wireless/ath/wil6210/ftm.c903
1 files changed, 903 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/wil6210/ftm.c b/drivers/net/wireless/ath/wil6210/ftm.c
new file mode 100644
index 000000000000..5cf07343a33c
--- /dev/null
+++ b/drivers/net/wireless/ath/wil6210/ftm.c
@@ -0,0 +1,903 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+#include <linux/etherdevice.h>
+#include <net/netlink.h>
+#include "wil6210.h"
+#include "ftm.h"
+#include "wmi.h"
+
+/* FTM session ID we use with FW */
+#define WIL_FTM_FW_SESSION_ID 1
+
+/* fixed spare allocation we reserve in NL messages we allocate */
+#define WIL_FTM_NL_EXTRA_ALLOC 32
+
+/* approx maximum length for FTM_MEAS_RESULT NL80211 event */
+#define WIL_FTM_MEAS_RESULT_MAX_LENGTH 2048
+
+/* timeout for waiting for standalone AOA measurement, milliseconds */
+#define WIL_AOA_MEASUREMENT_TIMEOUT 1000
+
+/* maximum number of allowed FTM measurements per burst */
+#define WIL_FTM_MAX_MEAS_PER_BURST 31
+
+/* initial token to use on non-secure FTM measurement */
+#define WIL_TOF_FTM_DEFAULT_INITIAL_TOKEN 2
+
+#define WIL_TOF_FTM_MAX_LCI_LENGTH (240)
+#define WIL_TOF_FTM_MAX_LCR_LENGTH (240)
+
+static const struct
+nla_policy wil_nl80211_loc_policy[QCA_WLAN_VENDOR_ATTR_LOC_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE] = { .type = NLA_U64 },
+ [QCA_WLAN_VENDOR_ATTR_LOC_CAPA] = { .type = NLA_NESTED },
+ [QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS] = { .type = NLA_NESTED },
+ [QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS] = { .type = NLA_NESTED },
+ [QCA_WLAN_VENDOR_ATTR_FTM_RESPONDER_ENABLE] = { .type = NLA_FLAG },
+ [QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS] = { .type = NLA_U32 },
+ [QCA_WLAN_VENDOR_ATTR_FTM_INITIAL_TOKEN] = { .type = NLA_U8 },
+ [QCA_WLAN_VENDOR_ATTR_AOA_TYPE] = { .type = NLA_U32 },
+ [QCA_WLAN_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK] = { .type = NLA_U32 },
+};
+
+static const struct
+nla_policy wil_nl80211_ftm_peer_policy[
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR] = { .len = ETH_ALEN },
+ [QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS] = { .type = NLA_U32 },
+ [QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS] = { .type = NLA_NESTED },
+ [QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID] = { .type = NLA_U8 },
+};
+
+static const struct
+nla_policy wil_nl80211_ftm_meas_param_policy[
+ QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST] = { .type = NLA_U8 },
+ [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP] = { .type = NLA_U8 },
+ [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION] = { .type = NLA_U8 },
+ [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD] = { .type = NLA_U16 },
+};
+
+static int wil_ftm_parse_meas_params(struct wil6210_priv *wil,
+ struct nlattr *attr,
+ struct wil_ftm_meas_params *params)
+{
+ struct nlattr *tb[QCA_WLAN_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;
+ }
+ rc = nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MAX,
+ attr, wil_nl80211_ftm_meas_param_policy);
+ if (rc) {
+ wil_err(wil, "invalid measurement params\n");
+ return rc;
+ }
+ if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST])
+ params->meas_per_burst = nla_get_u8(
+ tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST]);
+ if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP])
+ params->num_of_bursts_exp = nla_get_u8(
+ tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP]);
+ if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION])
+ params->burst_duration = nla_get_u8(
+ tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION]);
+ if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD])
+ params->burst_period = nla_get_u16(
+ tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD]);
+ return 0;
+}
+
+static int wil_ftm_validate_meas_params(struct wil6210_priv *wil,
+ struct wil_ftm_meas_params *params)
+{
+ /* temporary allow only single-burst */
+ if (params->meas_per_burst > WIL_FTM_MAX_MEAS_PER_BURST ||
+ params->num_of_bursts_exp != 0) {
+ wil_err(wil, "invalid measurement params\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wil_ftm_append_meas_params(struct wil6210_priv *wil,
+ struct sk_buff *msg,
+ struct wil_ftm_meas_params *params)
+{
+ struct nlattr *nl_p;
+
+ nl_p = nla_nest_start(
+ msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS_PARAMS);
+ if (!nl_p)
+ goto out_put_failure;
+ if (nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST,
+ params->meas_per_burst) ||
+ nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP,
+ params->num_of_bursts_exp) ||
+ nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION,
+ params->burst_duration) ||
+ nla_put_u16(msg, QCA_WLAN_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 wil_ftm_append_peer_meas_res(struct wil6210_priv *wil,
+ struct sk_buff *msg,
+ struct wil_ftm_peer_meas_res *res)
+{
+ struct nlattr *nl_mres, *nl_f;
+ int i;
+
+ if (nla_put(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MAC_ADDR,
+ ETH_ALEN, res->mac_addr) ||
+ nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_FLAGS,
+ res->flags) ||
+ nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS,
+ res->status))
+ goto out_put_failure;
+ if (res->status == QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_FAILED &&
+ nla_put_u8(msg,
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_VALUE_SECONDS,
+ res->value_seconds))
+ goto out_put_failure;
+ if (res->has_params &&
+ wil_ftm_append_meas_params(wil, msg, &res->params))
+ goto out_put_failure;
+ nl_mres = nla_nest_start(msg, QCA_WLAN_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 (nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T1,
+ res->meas[i].t1) ||
+ nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T2,
+ res->meas[i].t2) ||
+ nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T3,
+ res->meas[i].t3) ||
+ nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T4,
+ res->meas[i].t4))
+ goto out_put_failure;
+ nla_nest_end(msg, nl_f);
+ }
+ nla_nest_end(msg, nl_mres);
+ return 0;
+out_put_failure:
+ wil_err(wil, "fail to append peer result\n");
+ return -ENOBUFS;
+}
+
+static void wil_ftm_send_meas_result(struct wil6210_priv *wil,
+ struct wil_ftm_peer_meas_res *res)
+{
+ struct sk_buff *vendor_event = NULL;
+ struct nlattr *nl_res;
+ int rc = 0;
+
+ wil_dbg_misc(wil, "sending %d results for peer %pM\n",
+ res->n_meas, res->mac_addr);
+
+ vendor_event = cfg80211_vendor_event_alloc(
+ wil_to_wiphy(wil),
+ wil->wdev,
+ WIL_FTM_MEAS_RESULT_MAX_LENGTH,
+ QCA_NL80211_VENDOR_EVENT_FTM_MEAS_RESULT_INDEX,
+ GFP_KERNEL);
+ if (!vendor_event) {
+ wil_err(wil, "fail to allocate measurement result\n");
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (nla_put_u64(
+ vendor_event, QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE,
+ wil->ftm.session_cookie)) {
+ rc = -ENOBUFS;
+ goto out;
+ }
+
+ nl_res = nla_nest_start(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS);
+ if (!nl_res) {
+ rc = -ENOBUFS;
+ goto out;
+ }
+
+ rc = wil_ftm_append_peer_meas_res(wil, vendor_event, res);
+ if (rc)
+ goto out;
+
+ nla_nest_end(vendor_event, nl_res);
+ cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+ vendor_event = NULL;
+out:
+ if (vendor_event)
+ kfree_skb(vendor_event);
+ if (rc)
+ wil_err(wil, "send peer result failed, err %d\n", rc);
+}
+
+static void wil_ftm_send_peer_res(struct wil6210_priv *wil)
+{
+ if (!wil->ftm.has_ftm_res || !wil->ftm.ftm_res)
+ return;
+
+ wil_ftm_send_meas_result(wil, wil->ftm.ftm_res);
+ wil->ftm.has_ftm_res = 0;
+ wil->ftm.ftm_res->n_meas = 0;
+}
+
+static void wil_aoa_measurement_timeout(struct work_struct *work)
+{
+ struct wil_ftm_priv *ftm = container_of(work, struct wil_ftm_priv,
+ aoa_timeout_work);
+ struct wil6210_priv *wil = container_of(ftm, struct wil6210_priv, ftm);
+ struct wil_aoa_meas_result res;
+
+ wil_dbg_misc(wil, "AOA measurement timeout\n");
+
+ memset(&res, 0, sizeof(res));
+ ether_addr_copy(res.mac_addr, wil->ftm.aoa_peer_mac_addr);
+ res.type = wil->ftm.aoa_type;
+ res.status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED;
+ wil_aoa_cfg80211_meas_result(wil, &res);
+}
+
+static int
+wil_ftm_cfg80211_start_session(struct wil6210_priv *wil,
+ struct wil_ftm_session_request *request)
+{
+ int rc = 0;
+ bool has_lci = false, has_lcr = false;
+ u8 max_meas = 0, *ptr;
+ u32 i, cmd_len;
+ struct wmi_tof_session_start_cmd *cmd;
+
+ mutex_lock(&wil->ftm.lock);
+ if (wil->ftm.session_started) {
+ wil_err(wil, "FTM session already running\n");
+ rc = -EAGAIN;
+ goto out;
+ }
+ /* for now allow measurement to associated AP only */
+ if (!test_bit(wil_status_fwconnected, wil->status)) {
+ wil_err(wil, "must be associated\n");
+ rc = -ENOTSUPP;
+ goto out;
+ }
+
+ for (i = 0; i < request->n_peers; i++) {
+ if (request->peers[i].flags &
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI)
+ has_lci = true;
+ if (request->peers[i].flags &
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR)
+ has_lcr = true;
+ max_meas = max(max_meas,
+ request->peers[i].params.meas_per_burst);
+ }
+
+ wil->ftm.ftm_res = kzalloc(sizeof(*wil->ftm.ftm_res) +
+ max_meas * sizeof(struct wil_ftm_peer_meas) +
+ (has_lci ? WIL_TOF_FTM_MAX_LCI_LENGTH : 0) +
+ (has_lcr ? WIL_TOF_FTM_MAX_LCR_LENGTH : 0), GFP_KERNEL);
+ if (!wil->ftm.ftm_res) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ ptr = (u8 *)wil->ftm.ftm_res;
+ ptr += sizeof(struct wil_ftm_peer_meas_res) +
+ max_meas * sizeof(struct wil_ftm_peer_meas);
+ if (has_lci) {
+ wil->ftm.ftm_res->lci = ptr;
+ ptr += WIL_TOF_FTM_MAX_LCI_LENGTH;
+ }
+ if (has_lcr)
+ wil->ftm.ftm_res->lcr = ptr;
+ wil->ftm.max_ftm_meas = max_meas;
+
+ cmd_len = sizeof(struct wmi_tof_session_start_cmd) +
+ request->n_peers * sizeof(struct wmi_ftm_dest_info);
+ cmd = kzalloc(cmd_len, GFP_KERNEL);
+ if (!cmd) {
+ rc = -ENOMEM;
+ goto out_ftm_res;
+ }
+
+ cmd->session_id = cpu_to_le32(WIL_FTM_FW_SESSION_ID);
+ cmd->num_of_dest = cpu_to_le16(request->n_peers);
+ for (i = 0; i < request->n_peers; i++) {
+ ether_addr_copy(cmd->ftm_dest_info[i].dst_mac,
+ request->peers[i].mac_addr);
+ cmd->ftm_dest_info[i].channel = request->peers[i].channel;
+ if (request->peers[i].flags &
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_SECURE) {
+ cmd->ftm_dest_info[i].flags |=
+ WMI_TOF_SESSION_START_FLAG_SECURED;
+ cmd->ftm_dest_info[i].initial_token =
+ request->peers[i].secure_token_id;
+ } else {
+ cmd->ftm_dest_info[i].initial_token =
+ WIL_TOF_FTM_DEFAULT_INITIAL_TOKEN;
+ }
+ if (request->peers[i].flags &
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_ASAP)
+ cmd->ftm_dest_info[i].flags |=
+ WMI_TOF_SESSION_START_FLAG_ASAP;
+ if (request->peers[i].flags &
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI)
+ cmd->ftm_dest_info[i].flags |=
+ WMI_TOF_SESSION_START_FLAG_LCI_REQ;
+ if (request->peers[i].flags &
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR)
+ cmd->ftm_dest_info[i].flags |=
+ WMI_TOF_SESSION_START_FLAG_LCR_REQ;
+ cmd->ftm_dest_info[i].num_of_ftm_per_burst =
+ request->peers[i].params.meas_per_burst;
+ cmd->ftm_dest_info[i].num_of_bursts_exp =
+ request->peers[i].params.num_of_bursts_exp;
+ cmd->ftm_dest_info[i].burst_duration =
+ request->peers[i].params.burst_duration;
+ cmd->ftm_dest_info[i].burst_period =
+ cpu_to_le16(request->peers[i].params.burst_period);
+ }
+
+ rc = wmi_send(wil, WMI_TOF_SESSION_START_CMDID, cmd, cmd_len);
+ kfree(cmd);
+
+ if (rc)
+ goto out_ftm_res;
+
+ wil->ftm.session_cookie = request->session_cookie;
+ wil->ftm.session_started = 1;
+
+out_ftm_res:
+ if (rc) {
+ kfree(wil->ftm.ftm_res);
+ wil->ftm.ftm_res = NULL;
+ }
+out:
+ mutex_unlock(&wil->ftm.lock);
+ return rc;
+}
+
+static void
+wil_ftm_cfg80211_session_ended(struct wil6210_priv *wil, u32 status)
+{
+ struct sk_buff *vendor_event = NULL;
+
+ mutex_lock(&wil->ftm.lock);
+
+ if (!wil->ftm.session_started) {
+ wil_dbg_misc(wil, "FTM session not started, ignoring event\n");
+ goto out;
+ }
+
+ /* finish the session */
+ wil_dbg_misc(wil, "finishing FTM session\n");
+
+ /* send left-over results if any */
+ wil_ftm_send_peer_res(wil);
+
+ wil->ftm.session_started = 0;
+ kfree(wil->ftm.ftm_res);
+ wil->ftm.ftm_res = NULL;
+
+ vendor_event = cfg80211_vendor_event_alloc(
+ wil_to_wiphy(wil),
+ wil->wdev,
+ WIL_FTM_NL_EXTRA_ALLOC,
+ QCA_NL80211_VENDOR_EVENT_FTM_SESSION_DONE_INDEX,
+ GFP_KERNEL);
+ if (!vendor_event)
+ goto out;
+
+ if (nla_put_u64(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE,
+ wil->ftm.session_cookie) ||
+ nla_put_u32(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS, status)) {
+ wil_err(wil, "failed to fill session done event\n");
+ goto out;
+ }
+ cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+ vendor_event = NULL;
+out:
+ kfree_skb(vendor_event);
+ mutex_unlock(&wil->ftm.lock);
+}
+
+static void wil_aoa_timer_fn(ulong x)
+{
+ struct wil6210_priv *wil = (void *)x;
+
+ wil_dbg_misc(wil, "AOA timer\n");
+ schedule_work(&wil->ftm.aoa_timeout_work);
+}
+
+static int
+wil_aoa_cfg80211_start_measurement(struct wil6210_priv *wil,
+ struct wil_aoa_meas_request *request)
+{
+ int rc = 0;
+ struct cfg80211_bss *bss;
+ struct wmi_aoa_meas_cmd cmd;
+
+ mutex_lock(&wil->ftm.lock);
+
+ if (wil->ftm.aoa_started) {
+ wil_err(wil, "AOA measurement already running\n");
+ rc = -EAGAIN;
+ goto out;
+ }
+ if (request->type >= QCA_WLAN_VENDOR_ATTR_AOA_TYPE_MAX) {
+ wil_err(wil, "invalid AOA type: %d\n", request->type);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ bss = cfg80211_get_bss(wil_to_wiphy(wil), NULL, request->mac_addr,
+ NULL, 0,
+ IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
+ if (!bss) {
+ wil_err(wil, "Unable to find BSS\n");
+ rc = -ENOENT;
+ goto out;
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+ ether_addr_copy(cmd.mac_addr, request->mac_addr);
+ cmd.channel = bss->channel->hw_value - 1;
+ cmd.aoa_meas_type = request->type;
+
+ rc = wmi_send(wil, WMI_AOA_MEAS_CMDID, &cmd, sizeof(cmd));
+ if (rc)
+ goto out_bss;
+
+ ether_addr_copy(wil->ftm.aoa_peer_mac_addr, request->mac_addr);
+ mod_timer(&wil->ftm.aoa_timer,
+ jiffies + msecs_to_jiffies(WIL_AOA_MEASUREMENT_TIMEOUT));
+ wil->ftm.aoa_started = 1;
+out_bss:
+ cfg80211_put_bss(wil_to_wiphy(wil), bss);
+out:
+ mutex_unlock(&wil->ftm.lock);
+ return rc;
+}
+
+void wil_aoa_cfg80211_meas_result(struct wil6210_priv *wil,
+ struct wil_aoa_meas_result *result)
+{
+ struct sk_buff *vendor_event = NULL;
+
+ mutex_lock(&wil->ftm.lock);
+
+ if (!wil->ftm.aoa_started) {
+ wil_info(wil, "AOA not started, not sending result\n");
+ goto out;
+ }
+
+ wil_dbg_misc(wil, "sending AOA measurement result\n");
+
+ vendor_event = cfg80211_vendor_event_alloc(
+ wil_to_wiphy(wil),
+ wil->wdev,
+ result->length + WIL_FTM_NL_EXTRA_ALLOC,
+ QCA_NL80211_VENDOR_EVENT_AOA_MEAS_RESULT_INDEX,
+ GFP_KERNEL);
+ if (!vendor_event) {
+ wil_err(wil, "fail to allocate measurement result\n");
+ goto out;
+ }
+
+ if (nla_put(vendor_event, QCA_WLAN_VENDOR_ATTR_MAC_ADDR,
+ ETH_ALEN, result->mac_addr) ||
+ nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_AOA_TYPE,
+ result->type) ||
+ nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS,
+ result->status) ||
+ nla_put_u32(vendor_event,
+ QCA_WLAN_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK,
+ result->antenna_array_mask)) {
+ wil_err(wil, "failed to fill vendor event\n");
+ goto out;
+ }
+
+ if (result->length > 0 &&
+ nla_put(vendor_event, QCA_WLAN_VENDOR_ATTR_AOA_MEAS_RESULT,
+ result->length, result->data)) {
+ wil_err(wil, "failed to fill vendor event with AOA data\n");
+ goto out;
+ }
+
+ cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+
+ del_timer_sync(&wil->ftm.aoa_timer);
+ wil->ftm.aoa_started = 0;
+out:
+ mutex_unlock(&wil->ftm.lock);
+}
+
+void wil_ftm_evt_session_ended(struct wil6210_priv *wil,
+ struct wmi_tof_session_end_event *evt)
+{
+ u32 status;
+
+ switch (evt->status) {
+ case WMI_TOF_SESSION_END_NO_ERROR:
+ status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_OK;
+ break;
+ case WMI_TOF_SESSION_END_PARAMS_ERROR:
+ status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_INVALID;
+ break;
+ case WMI_TOF_SESSION_END_FAIL:
+ status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_FAILED;
+ break;
+ case WMI_TOF_SESSION_END_ABORTED:
+ status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED;
+ break;
+ default:
+ status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_FAILED;
+ break;
+ }
+
+ wil_ftm_cfg80211_session_ended(wil, status);
+}
+
+void wil_ftm_evt_per_dest_res(struct wil6210_priv *wil,
+ struct wmi_tof_ftm_per_dest_res_event *evt)
+{
+ u32 i, index;
+ __le64 tmp = 0;
+ u8 n_meas;
+
+ mutex_lock(&wil->ftm.lock);
+
+ if (!wil->ftm.session_started || !wil->ftm.ftm_res) {
+ wil_dbg_misc(wil, "Session not running, ignoring res event\n");
+ goto out;
+ }
+ if (wil->ftm.has_ftm_res &&
+ !ether_addr_equal(evt->dst_mac, wil->ftm.ftm_res->mac_addr)) {
+ wil_dbg_misc(wil,
+ "Results for previous peer not properly terminated\n");
+ wil_ftm_send_peer_res(wil);
+ }
+
+ if (!wil->ftm.has_ftm_res) {
+ ether_addr_copy(wil->ftm.ftm_res->mac_addr, evt->dst_mac);
+ wil->ftm.has_ftm_res = 1;
+ }
+
+ n_meas = evt->actual_ftm_per_burst;
+ switch (evt->status) {
+ case WMI_PER_DEST_RES_NO_ERROR:
+ wil->ftm.ftm_res->status =
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK;
+ break;
+ case WMI_PER_DEST_RES_TX_RX_FAIL:
+ /* FW reports corrupted results here, discard. */
+ n_meas = 0;
+ wil->ftm.ftm_res->status =
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK;
+ break;
+ case WMI_PER_DEST_RES_PARAM_DONT_MATCH:
+ wil->ftm.ftm_res->status =
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID;
+ break;
+ default:
+ wil_err(wil, "unexpected status %d\n", evt->status);
+ wil->ftm.ftm_res->status =
+ QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID;
+ break;
+ }
+
+ for (i = 0; i < n_meas; i++) {
+ index = wil->ftm.ftm_res->n_meas;
+ if (index >= wil->ftm.max_ftm_meas) {
+ wil_dbg_misc(wil, "Too many measurements, some lost\n");
+ break;
+ }
+ memcpy(&tmp, evt->responder_ftm_res[i].t1,
+ sizeof(evt->responder_ftm_res[i].t1));
+ wil->ftm.ftm_res->meas[index].t1 = le64_to_cpu(tmp);
+ memcpy(&tmp, evt->responder_ftm_res[i].t2,
+ sizeof(evt->responder_ftm_res[i].t2));
+ wil->ftm.ftm_res->meas[index].t2 = le64_to_cpu(tmp);
+ memcpy(&tmp, evt->responder_ftm_res[i].t3,
+ sizeof(evt->responder_ftm_res[i].t3));
+ wil->ftm.ftm_res->meas[index].t3 = le64_to_cpu(tmp);
+ memcpy(&tmp, evt->responder_ftm_res[i].t4,
+ sizeof(evt->responder_ftm_res[i].t4));
+ wil->ftm.ftm_res->meas[index].t4 = le64_to_cpu(tmp);
+ wil->ftm.ftm_res->n_meas++;
+ }
+
+ if (evt->flags & WMI_PER_DEST_RES_BURST_REPORT_END)
+ wil_ftm_send_peer_res(wil);
+out:
+ mutex_unlock(&wil->ftm.lock);
+}
+
+void wil_aoa_evt_meas(struct wil6210_priv *wil,
+ struct wmi_aoa_meas_event *evt,
+ int len)
+{
+ int data_len = len - offsetof(struct wmi_aoa_meas_event, meas_data);
+ struct wil_aoa_meas_result *res;
+
+ data_len = min_t(int, le16_to_cpu(evt->length), data_len);
+
+ res = kmalloc(sizeof(*res) + data_len, GFP_KERNEL);
+ if (!res)
+ return;
+
+ ether_addr_copy(res->mac_addr, evt->mac_addr);
+ res->type = evt->aoa_meas_type;
+ res->antenna_array_mask = le32_to_cpu(evt->meas_rf_mask);
+ res->status = evt->meas_status;
+ res->length = data_len;
+ memcpy(res->data, evt->meas_data, data_len);
+
+ wil_dbg_misc(wil, "AOA result status %d type %d mask %d length %d\n",
+ res->status, res->type,
+ res->antenna_array_mask, res->length);
+
+ wil_aoa_cfg80211_meas_result(wil, res);
+ kfree(res);
+}
+
+int wil_ftm_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+ struct sk_buff *skb;
+ struct nlattr *attr;
+
+ if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities))
+ return -ENOTSUPP;
+
+ /* we should get the capabilities from the FW. for now,
+ * report dummy capabilities for one shot measurement
+ */
+ skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 128);
+ if (!skb)
+ return -ENOMEM;
+ attr = nla_nest_start(skb, QCA_WLAN_VENDOR_ATTR_LOC_CAPA);
+ if (!attr ||
+ nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAGS,
+ QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_RESPONDER |
+ QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_INITIATOR |
+ QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_ASAP |
+ QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_AOA) ||
+ nla_put_u16(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_SESSIONS,
+ 1) ||
+ nla_put_u16(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_PEERS, 1) ||
+ nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_BURSTS_EXP,
+ 0) ||
+ nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_MEAS_PER_BURST,
+ 4) ||
+ nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_AOA_CAPA_SUPPORTED_TYPES,
+ BIT(QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE))) {
+ wil_err(wil, "fail to fill get_capabilities reply\n");
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ nla_nest_end(skb, attr);
+
+ return cfg80211_vendor_cmd_reply(skb);
+}
+
+int wil_ftm_start_session(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+ struct wil_ftm_session_request *request;
+ struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_LOC_MAX + 1];
+ struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX + 1];
+ struct nlattr *peer;
+ int rc, n_peers = 0, index = 0, tmp;
+ struct cfg80211_bss *bss;
+
+ if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities))
+ return -ENOTSUPP;
+
+ rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_LOC_MAX, data, data_len,
+ wil_nl80211_loc_policy);
+ if (rc) {
+ wil_err(wil, "Invalid ATTR\n");
+ return rc;
+ }
+
+ if (!tb[QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS]) {
+ wil_err(wil, "no peers specified\n");
+ return -EINVAL;
+ }
+
+ if (!tb[QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE]) {
+ wil_err(wil, "session cookie not specified\n");
+ return -EINVAL;
+ }
+
+ nla_for_each_nested(peer, tb[QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS],
+ tmp)
+ n_peers++;
+
+ if (!n_peers) {
+ wil_err(wil, "empty peer list\n");
+ return -EINVAL;
+ }
+
+ /* for now only allow measurement for a single peer */
+ if (n_peers != 1) {
+ wil_err(wil, "only single peer allowed\n");
+ return -EINVAL;
+ }
+
+ request = kzalloc(sizeof(*request) +
+ n_peers * sizeof(struct wil_ftm_meas_peer_info),
+ GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->session_cookie =
+ nla_get_u64(tb[QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE]);
+ request->n_peers = n_peers;
+ nla_for_each_nested(peer, tb[QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS],
+ tmp) {
+ rc = nla_parse_nested(tb2, QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX,
+ peer, wil_nl80211_ftm_peer_policy);
+ if (rc) {
+ wil_err(wil, "Invalid peer ATTR\n");
+ goto out;
+ }
+ if (!tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR] ||
+ nla_len(tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR])
+ != ETH_ALEN) {
+ wil_err(wil, "Peer MAC address missing or invalid\n");
+ rc = -EINVAL;
+ goto out;
+ }
+ memcpy(request->peers[index].mac_addr,
+ nla_data(tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR]),
+ ETH_ALEN);
+ bss = cfg80211_get_bss(wiphy, NULL,
+ request->peers[index].mac_addr, NULL, 0,
+ IEEE80211_BSS_TYPE_ANY,
+ IEEE80211_PRIVACY_ANY);
+ if (!bss) {
+ wil_err(wil, "invalid bss at index %d\n", index);
+ rc = -ENOENT;
+ goto out;
+ }
+ request->peers[index].channel = bss->channel->hw_value - 1;
+ cfg80211_put_bss(wiphy, bss);
+ if (tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS])
+ request->peers[index].flags = nla_get_u32(
+ tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS]);
+ if (tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID])
+ request->peers[index].secure_token_id = nla_get_u8(
+ tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID]);
+ rc = wil_ftm_parse_meas_params(
+ wil,
+ tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS],
+ &request->peers[index].params);
+ if (!rc)
+ rc = wil_ftm_validate_meas_params(
+ wil, &request->peers[index].params);
+ if (rc)
+ goto out;
+ index++;
+ }
+
+ rc = wil_ftm_cfg80211_start_session(wil, request);
+out:
+ kfree(request);
+ return rc;
+}
+
+int wil_ftm_abort_session(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+
+ wil_dbg_misc(wil, "stub\n");
+ return -ENOTSUPP;
+}
+
+int wil_ftm_configure_responder(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+
+ wil_dbg_misc(wil, "stub\n");
+ return -ENOTSUPP;
+}
+
+int wil_aoa_start_measurement(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+ struct wil_aoa_meas_request request;
+ struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_LOC_MAX + 1];
+ int rc;
+
+ if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities))
+ return -ENOTSUPP;
+
+ wil_dbg_misc(wil, "AOA start measurement\n");
+
+ rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_LOC_MAX, data, data_len,
+ wil_nl80211_loc_policy);
+ if (rc) {
+ wil_err(wil, "Invalid ATTR\n");
+ return rc;
+ }
+
+ if (!tb[QCA_WLAN_VENDOR_ATTR_MAC_ADDR] ||
+ !tb[QCA_WLAN_VENDOR_ATTR_AOA_TYPE]) {
+ wil_err(wil, "Must specify MAC address and type\n");
+ return -EINVAL;
+ }
+
+ memset(&request, 0, sizeof(request));
+ ether_addr_copy(request.mac_addr,
+ nla_data(tb[QCA_WLAN_VENDOR_ATTR_MAC_ADDR]));
+ request.type = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_AOA_TYPE]);
+
+ rc = wil_aoa_cfg80211_start_measurement(wil, &request);
+ return rc;
+}
+
+int wil_aoa_abort_measurement(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int data_len)
+{
+ struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+
+ wil_dbg_misc(wil, "stub\n");
+ return -ENOTSUPP;
+}
+
+void wil_ftm_init(struct wil6210_priv *wil)
+{
+ mutex_init(&wil->ftm.lock);
+ setup_timer(&wil->ftm.aoa_timer, wil_aoa_timer_fn, (ulong)wil);
+ INIT_WORK(&wil->ftm.aoa_timeout_work, wil_aoa_measurement_timeout);
+}
+
+void wil_ftm_deinit(struct wil6210_priv *wil)
+{
+ del_timer_sync(&wil->ftm.aoa_timer);
+ cancel_work_sync(&wil->ftm.aoa_timeout_work);
+ kfree(wil->ftm.ftm_res);
+}
+
+void wil_ftm_stop_operations(struct wil6210_priv *wil)
+{
+ wil_ftm_cfg80211_session_ended(
+ wil, QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED);
+}