/* * Copyright (C) 2015 Spreadtrum Communications Inc. * * Abstract : This file is an implementation for cfg80211 subsystem * * Authors: * Chaojie Xu * * 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 #include #include #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) */