Path: blob/main/sys/contrib/dev/iwlwifi/mld/ftm-initiator.c
48286 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2025 Intel Corporation3*/4#include <linux/etherdevice.h>5#include <linux/math64.h>6#include <net/cfg80211.h>7#include "mld.h"8#include "iface.h"9#include "phy.h"10#include "iwl-io.h"11#include "iwl-prph.h"12#include "constants.h"13#include "fw/api/location.h"14#include "ftm-initiator.h"1516static void iwl_mld_ftm_cmd_common(struct iwl_mld *mld,17struct ieee80211_vif *vif,18struct iwl_tof_range_req_cmd *cmd,19struct cfg80211_pmsr_request *req)20{21int i;2223cmd->initiator_flags =24cpu_to_le32(IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM |25IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT);26cmd->request_id = req->cookie;27cmd->num_of_ap = req->n_peers;2829/* Use a large value for "no timeout". Don't use the maximum value30* because of fw limitations.31*/32if (req->timeout)33cmd->req_timeout_ms = cpu_to_le32(min(req->timeout, 0xfffff));34else35cmd->req_timeout_ms = cpu_to_le32(0xfffff);3637memcpy(cmd->macaddr_template, req->mac_addr, ETH_ALEN);38for (i = 0; i < ETH_ALEN; i++)39cmd->macaddr_mask[i] = ~req->mac_addr_mask[i];4041if (vif->cfg.assoc) {42memcpy(cmd->range_req_bssid, vif->bss_conf.bssid, ETH_ALEN);4344/* AP's TSF is only relevant if associated */45for (i = 0; i < req->n_peers; i++) {46if (req->peers[i].report_ap_tsf) {47struct iwl_mld_vif *mld_vif =48iwl_mld_vif_from_mac80211(vif);4950cmd->tsf_mac_id = cpu_to_le32(mld_vif->fw_id);51return;52}53}54} else {55eth_broadcast_addr(cmd->range_req_bssid);56}5758/* Don't report AP's TSF */59cmd->tsf_mac_id = cpu_to_le32(0xff);60}6162static int63iwl_mld_ftm_set_target_chandef(struct iwl_mld *mld,64struct cfg80211_pmsr_request_peer *peer,65struct iwl_tof_range_req_ap_entry *target)66{67u32 freq = peer->chandef.chan->center_freq;6869target->channel_num = ieee80211_frequency_to_channel(freq);7071switch (peer->chandef.width) {72case NL80211_CHAN_WIDTH_20_NOHT:73target->format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY;74target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS;75break;76case NL80211_CHAN_WIDTH_20:77target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT;78target->format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS;79break;80case NL80211_CHAN_WIDTH_40:81target->format_bw = IWL_LOCATION_FRAME_FORMAT_HT;82target->format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS;83break;84case NL80211_CHAN_WIDTH_80:85target->format_bw = IWL_LOCATION_FRAME_FORMAT_VHT;86target->format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS;87break;88case NL80211_CHAN_WIDTH_160:89target->format_bw = IWL_LOCATION_FRAME_FORMAT_HE;90target->format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS;91break;92default:93IWL_ERR(mld, "Unsupported BW in FTM request (%d)\n",94peer->chandef.width);95return -EINVAL;96}9798/* non EDCA based measurement must use HE preamble */99if (peer->ftm.trigger_based || peer->ftm.non_trigger_based)100target->format_bw |= IWL_LOCATION_FRAME_FORMAT_HE;101102target->ctrl_ch_position =103(peer->chandef.width > NL80211_CHAN_WIDTH_20) ?104iwl_mld_get_fw_ctrl_pos(&peer->chandef) : 0;105106target->band = iwl_mld_nl80211_band_to_fw(peer->chandef.chan->band);107return 0;108}109110#define FTM_SET_FLAG(flag) (target->initiator_ap_flags |= \111cpu_to_le32(IWL_INITIATOR_AP_FLAGS_##flag))112113static void114iwl_mld_ftm_set_target_flags(struct iwl_mld *mld,115struct cfg80211_pmsr_request_peer *peer,116struct iwl_tof_range_req_ap_entry *target)117{118target->initiator_ap_flags = cpu_to_le32(0);119120if (peer->ftm.asap)121FTM_SET_FLAG(ASAP);122123if (peer->ftm.request_lci)124FTM_SET_FLAG(LCI_REQUEST);125126if (peer->ftm.request_civicloc)127FTM_SET_FLAG(CIVIC_REQUEST);128129if (IWL_MLD_FTM_INITIATOR_DYNACK)130FTM_SET_FLAG(DYN_ACK);131132if (IWL_MLD_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_LINEAR_REG)133FTM_SET_FLAG(ALGO_LR);134else if (IWL_MLD_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_FFT)135FTM_SET_FLAG(ALGO_FFT);136137if (peer->ftm.trigger_based)138FTM_SET_FLAG(TB);139else if (peer->ftm.non_trigger_based)140FTM_SET_FLAG(NON_TB);141142if ((peer->ftm.trigger_based || peer->ftm.non_trigger_based) &&143peer->ftm.lmr_feedback)144FTM_SET_FLAG(LMR_FEEDBACK);145}146147static void iwl_mld_ftm_set_sta(struct iwl_mld *mld, struct ieee80211_vif *vif,148struct cfg80211_pmsr_request_peer *peer,149struct iwl_tof_range_req_ap_entry *target)150{151struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);152u32 sta_id_mask;153154target->sta_id = IWL_INVALID_STA;155156/* TODO: add ftm_unprotected debugfs support */157158if (!vif->cfg.assoc || !mld_vif->ap_sta)159return;160161sta_id_mask = iwl_mld_fw_sta_id_mask(mld, mld_vif->ap_sta);162if (WARN_ON(hweight32(sta_id_mask) != 1))163return;164165target->sta_id = __ffs(sta_id_mask);166167if (mld_vif->ap_sta->mfp &&168(peer->ftm.trigger_based || peer->ftm.non_trigger_based))169FTM_SET_FLAG(PMF);170}171172static int173iwl_mld_ftm_set_target(struct iwl_mld *mld, struct ieee80211_vif *vif,174struct cfg80211_pmsr_request_peer *peer,175struct iwl_tof_range_req_ap_entry *target)176{177u32 i2r_max_sts;178int ret;179180ret = iwl_mld_ftm_set_target_chandef(mld, peer, target);181if (ret)182return ret;183184memcpy(target->bssid, peer->addr, ETH_ALEN);185target->burst_period = cpu_to_le16(peer->ftm.burst_period);186target->samples_per_burst = peer->ftm.ftms_per_burst;187target->num_of_bursts = peer->ftm.num_bursts_exp;188iwl_mld_ftm_set_target_flags(mld, peer, target);189iwl_mld_ftm_set_sta(mld, vif, peer, target);190191/* TODO: add secured ranging support */192193i2r_max_sts = IWL_MLD_FTM_I2R_MAX_STS > 1 ? 1 :194IWL_MLD_FTM_I2R_MAX_STS;195196target->r2i_ndp_params = IWL_MLD_FTM_R2I_MAX_REP |197(IWL_MLD_FTM_R2I_MAX_STS << IWL_LOCATION_MAX_STS_POS) |198(IWL_MLD_FTM_R2I_MAX_TOTAL_LTF << IWL_LOCATION_TOTAL_LTF_POS);199target->i2r_ndp_params = IWL_MLD_FTM_I2R_MAX_REP |200(i2r_max_sts << IWL_LOCATION_MAX_STS_POS) |201(IWL_MLD_FTM_I2R_MAX_TOTAL_LTF << IWL_LOCATION_TOTAL_LTF_POS);202203if (peer->ftm.non_trigger_based) {204target->min_time_between_msr =205cpu_to_le16(IWL_MLD_FTM_NON_TB_MIN_TIME_BETWEEN_MSR);206target->burst_period =207cpu_to_le16(IWL_MLD_FTM_NON_TB_MAX_TIME_BETWEEN_MSR);208} else {209target->min_time_between_msr = cpu_to_le16(0);210}211212/* TODO: Beacon interval is currently unknown, so use the common value213* of 100 TUs.214*/215target->beacon_interval = cpu_to_le16(100);216217return 0;218}219220int iwl_mld_ftm_start(struct iwl_mld *mld, struct ieee80211_vif *vif,221struct cfg80211_pmsr_request *req)222{223struct iwl_tof_range_req_cmd cmd;224struct iwl_host_cmd hcmd = {225.id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD),226.dataflags[0] = IWL_HCMD_DFL_DUP,227.data[0] = &cmd,228.len[0] = sizeof(cmd),229};230u8 i;231int ret;232233lockdep_assert_wiphy(mld->wiphy);234235if (mld->ftm_initiator.req)236return -EBUSY;237238if (req->n_peers > ARRAY_SIZE(cmd.ap))239return -EINVAL;240241memset(&cmd, 0, sizeof(cmd));242243iwl_mld_ftm_cmd_common(mld, vif, (void *)&cmd, req);244245for (i = 0; i < cmd.num_of_ap; i++) {246struct cfg80211_pmsr_request_peer *peer = &req->peers[i];247struct iwl_tof_range_req_ap_entry *target = &cmd.ap[i];248249ret = iwl_mld_ftm_set_target(mld, vif, peer, target);250if (ret)251return ret;252}253254/* TODO: get the status from the response*/255ret = iwl_mld_send_cmd(mld, &hcmd);256if (!ret) {257mld->ftm_initiator.req = req;258mld->ftm_initiator.req_wdev = ieee80211_vif_to_wdev(vif);259}260261return ret;262}263264static void iwl_mld_ftm_reset(struct iwl_mld *mld)265{266lockdep_assert_wiphy(mld->wiphy);267268mld->ftm_initiator.req = NULL;269mld->ftm_initiator.req_wdev = NULL;270memset(mld->ftm_initiator.responses, 0,271sizeof(mld->ftm_initiator.responses));272}273274static int iwl_mld_ftm_range_resp_valid(struct iwl_mld *mld, u8 request_id,275u8 num_of_aps)276{277if (IWL_FW_CHECK(mld, request_id != (u8)mld->ftm_initiator.req->cookie,278"Request ID mismatch, got %u, active %u\n",279request_id, (u8)mld->ftm_initiator.req->cookie))280return -EINVAL;281282if (IWL_FW_CHECK(mld, num_of_aps > mld->ftm_initiator.req->n_peers ||283num_of_aps > IWL_TOF_MAX_APS,284"FTM range response: invalid num of APs (%u)\n",285num_of_aps))286return -EINVAL;287288return 0;289}290291static int iwl_mld_ftm_find_peer(struct cfg80211_pmsr_request *req,292const u8 *addr)293{294for (int i = 0; i < req->n_peers; i++) {295struct cfg80211_pmsr_request_peer *peer = &req->peers[i];296297if (ether_addr_equal_unaligned(peer->addr, addr))298return i;299}300301return -ENOENT;302}303304static void iwl_mld_debug_range_resp(struct iwl_mld *mld, u8 index,305struct cfg80211_pmsr_result *res)306{307s64 rtt_avg = div_s64(res->ftm.rtt_avg * 100, 6666);308309IWL_DEBUG_INFO(mld, "entry %d\n", index);310IWL_DEBUG_INFO(mld, "\tstatus: %d\n", res->status);311IWL_DEBUG_INFO(mld, "\tBSSID: %pM\n", res->addr);312IWL_DEBUG_INFO(mld, "\thost time: %llu\n", res->host_time);313IWL_DEBUG_INFO(mld, "\tburst index: %d\n", res->ftm.burst_index);314IWL_DEBUG_INFO(mld, "\tsuccess num: %u\n", res->ftm.num_ftmr_successes);315IWL_DEBUG_INFO(mld, "\trssi: %d\n", res->ftm.rssi_avg);316IWL_DEBUG_INFO(mld, "\trssi spread: %d\n", res->ftm.rssi_spread);317IWL_DEBUG_INFO(mld, "\trtt: %lld\n", res->ftm.rtt_avg);318IWL_DEBUG_INFO(mld, "\trtt var: %llu\n", res->ftm.rtt_variance);319IWL_DEBUG_INFO(mld, "\trtt spread: %llu\n", res->ftm.rtt_spread);320IWL_DEBUG_INFO(mld, "\tdistance: %lld\n", rtt_avg);321}322323void iwl_mld_handle_ftm_resp_notif(struct iwl_mld *mld,324struct iwl_rx_packet *pkt)325{326struct iwl_tof_range_rsp_ntfy *fw_resp = (void *)pkt->data;327u8 num_of_aps, last_in_batch;328329if (IWL_FW_CHECK(mld, !mld->ftm_initiator.req,330"FTM response without a pending request\n"))331return;332333if (iwl_mld_ftm_range_resp_valid(mld, fw_resp->request_id,334fw_resp->num_of_aps))335return;336337num_of_aps = fw_resp->num_of_aps;338last_in_batch = fw_resp->last_report;339340IWL_DEBUG_INFO(mld, "Range response received\n");341IWL_DEBUG_INFO(mld, "request id: %llu, num of entries: %u\n",342mld->ftm_initiator.req->cookie, num_of_aps);343344for (int i = 0; i < num_of_aps; i++) {345struct cfg80211_pmsr_result result = {};346struct iwl_tof_range_rsp_ap_entry_ntfy *fw_ap;347int peer_idx;348349fw_ap = &fw_resp->ap[i];350result.final = fw_ap->last_burst;351result.ap_tsf = le32_to_cpu(fw_ap->start_tsf);352result.ap_tsf_valid = 1;353354peer_idx = iwl_mld_ftm_find_peer(mld->ftm_initiator.req,355fw_ap->bssid);356if (peer_idx < 0) {357IWL_WARN(mld,358"Unknown address (%pM, target #%d) in FTM response\n",359fw_ap->bssid, i);360continue;361}362363switch (fw_ap->measure_status) {364case IWL_TOF_ENTRY_SUCCESS:365result.status = NL80211_PMSR_STATUS_SUCCESS;366break;367case IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT:368result.status = NL80211_PMSR_STATUS_TIMEOUT;369break;370case IWL_TOF_ENTRY_NO_RESPONSE:371result.status = NL80211_PMSR_STATUS_FAILURE;372result.ftm.failure_reason =373NL80211_PMSR_FTM_FAILURE_NO_RESPONSE;374break;375case IWL_TOF_ENTRY_REQUEST_REJECTED:376result.status = NL80211_PMSR_STATUS_FAILURE;377result.ftm.failure_reason =378NL80211_PMSR_FTM_FAILURE_PEER_BUSY;379result.ftm.busy_retry_time = fw_ap->refusal_period;380break;381default:382result.status = NL80211_PMSR_STATUS_FAILURE;383result.ftm.failure_reason =384NL80211_PMSR_FTM_FAILURE_UNSPECIFIED;385break;386}387memcpy(result.addr, fw_ap->bssid, ETH_ALEN);388389/* TODO: convert the timestamp from the result to systime */390result.host_time = ktime_get_boottime_ns();391392result.type = NL80211_PMSR_TYPE_FTM;393result.ftm.burst_index = mld->ftm_initiator.responses[peer_idx];394mld->ftm_initiator.responses[peer_idx]++;395result.ftm.rssi_avg = fw_ap->rssi;396result.ftm.rssi_avg_valid = 1;397result.ftm.rssi_spread = fw_ap->rssi_spread;398result.ftm.rssi_spread_valid = 1;399result.ftm.rtt_avg = (s32)le32_to_cpu(fw_ap->rtt);400result.ftm.rtt_avg_valid = 1;401result.ftm.rtt_variance = le32_to_cpu(fw_ap->rtt_variance);402result.ftm.rtt_variance_valid = 1;403result.ftm.rtt_spread = le32_to_cpu(fw_ap->rtt_spread);404result.ftm.rtt_spread_valid = 1;405406cfg80211_pmsr_report(mld->ftm_initiator.req_wdev,407mld->ftm_initiator.req,408&result, GFP_KERNEL);409410if (fw_has_api(&mld->fw->ucode_capa,411IWL_UCODE_TLV_API_FTM_RTT_ACCURACY))412IWL_DEBUG_INFO(mld, "RTT confidence: %u\n",413fw_ap->rttConfidence);414415iwl_mld_debug_range_resp(mld, i, &result);416}417418if (last_in_batch) {419cfg80211_pmsr_complete(mld->ftm_initiator.req_wdev,420mld->ftm_initiator.req,421GFP_KERNEL);422iwl_mld_ftm_reset(mld);423}424}425426void iwl_mld_ftm_restart_cleanup(struct iwl_mld *mld)427{428struct cfg80211_pmsr_result result = {429.status = NL80211_PMSR_STATUS_FAILURE,430.final = 1,431.host_time = ktime_get_boottime_ns(),432.type = NL80211_PMSR_TYPE_FTM,433};434435if (!mld->ftm_initiator.req)436return;437438for (int i = 0; i < mld->ftm_initiator.req->n_peers; i++) {439memcpy(result.addr, mld->ftm_initiator.req->peers[i].addr,440ETH_ALEN);441442cfg80211_pmsr_report(mld->ftm_initiator.req_wdev,443mld->ftm_initiator.req,444&result, GFP_KERNEL);445}446447cfg80211_pmsr_complete(mld->ftm_initiator.req_wdev,448mld->ftm_initiator.req, GFP_KERNEL);449iwl_mld_ftm_reset(mld);450}451452453