Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce792918 authored by Gregory Greenman's avatar Gregory Greenman Committed by Emmanuel Grumbach
Browse files

iwlwifi: mvm: add basic Time of Flight (802.11mc FTM) support



ToF is a time based method for measurement of the WiFi device
location within a WiFi environment. The driver functionality provided
by this patch is the interface for communication with FW and receiving
location related updates from the FW. The interface provided by this
patch is via debugfs.

Signed-off-by: default avatarGregory Greenman <gregory.greenman@intel.com>
Reviewed-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
parent 6bcb00f6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -280,6 +280,7 @@ typedef unsigned int __bitwise__ iwl_ucode_tlv_capa_t;
 * @IWL_UCODE_TLV_CAPA_LAR_SUPPORT: supports Location Aware Regulatory
 * @IWL_UCODE_TLV_CAPA_UMAC_SCAN: supports UMAC scan.
 * @IWL_UCODE_TLV_CAPA_BEAMFORMER: supports Beamformer
 * @IWL_UCODE_TLV_CAPA_TOF_SUPPORT: supports Time of Flight (802.11mc FTM)
 * @IWL_UCODE_TLV_CAPA_TDLS_SUPPORT: support basic TDLS functionality
 * @IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT: supports insertion of current
 *	tx power value into TPC Report action frame and Link Measurement Report
@@ -307,6 +308,7 @@ enum iwl_ucode_tlv_capa {
	IWL_UCODE_TLV_CAPA_LAR_SUPPORT			= (__force iwl_ucode_tlv_capa_t)1,
	IWL_UCODE_TLV_CAPA_UMAC_SCAN			= (__force iwl_ucode_tlv_capa_t)2,
	IWL_UCODE_TLV_CAPA_BEAMFORMER			= (__force iwl_ucode_tlv_capa_t)3,
	IWL_UCODE_TLV_CAPA_TOF_SUPPORT                  = (__force iwl_ucode_tlv_capa_t)5,
	IWL_UCODE_TLV_CAPA_TDLS_SUPPORT			= (__force iwl_ucode_tlv_capa_t)6,
	IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT	= (__force iwl_ucode_tlv_capa_t)8,
	IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT	= (__force iwl_ucode_tlv_capa_t)9,
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ iwlmvm-y += power.o coex.o coex_legacy.o
iwlmvm-y += tt.o offloading.o tdls.o
iwlmvm-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o debugfs-vif.o
iwlmvm-$(CONFIG_IWLWIFI_LEDS) += led.o
iwlmvm-y += tof.o
iwlmvm-$(CONFIG_PM_SLEEP) += d3.o

ccflags-y += -D__CHECK_ENDIAN__ -I$(src)/../
+1 −0
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@
#define IWL_MVM_QUOTA_THRESHOLD			4
#define IWL_MVM_RS_RSSI_BASED_INIT_RATE         0
#define IWL_MVM_RS_DISABLE_P2P_MIMO		0
#define IWL_MVM_TOF_IS_RESPONDER		0
#define IWL_MVM_RS_NUM_TRY_BEFORE_ANT_TOGGLE    1
#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE      2
#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE_TW   1
+751 −0
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@
 *
 *****************************************************************************/
#include "mvm.h"
#include "fw-api-tof.h"
#include "debugfs.h"

static void iwl_dbgfs_update_pm(struct iwl_mvm *mvm,
@@ -497,6 +498,731 @@ static ssize_t iwl_dbgfs_bf_params_read(struct file *file,
	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
}

static inline char *iwl_dbgfs_is_match(char *name, char *buf)
{
	int len = strlen(name);

	return !strncmp(name, buf, len) ? buf + len : NULL;
}

static ssize_t iwl_dbgfs_tof_enable_write(struct ieee80211_vif *vif,
					  char *buf,
					  size_t count, loff_t *ppos)
{
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	int value, ret = -EINVAL;
	char *data;

	mutex_lock(&mvm->mutex);

	data = iwl_dbgfs_is_match("tof_disabled=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.tof_cfg.tof_disabled = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("one_sided_disabled=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.tof_cfg.one_sided_disabled = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("is_debug_mode=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.tof_cfg.is_debug_mode = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("is_buf=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.tof_cfg.is_buf_required = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("send_tof_cfg=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0 && value) {
			ret = iwl_mvm_tof_config_cmd(mvm);
			goto out;
		}
	}

out:
	mutex_unlock(&mvm->mutex);

	return ret ?: count;
}

static ssize_t iwl_dbgfs_tof_enable_read(struct file *file,
					 char __user *user_buf,
					 size_t count, loff_t *ppos)
{
	struct ieee80211_vif *vif = file->private_data;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	char buf[256];
	int pos = 0;
	const size_t bufsz = sizeof(buf);
	struct iwl_tof_config_cmd *cmd;

	cmd = &mvm->tof_data.tof_cfg;

	mutex_lock(&mvm->mutex);

	pos += scnprintf(buf + pos, bufsz - pos, "tof_disabled = %d\n",
			 cmd->tof_disabled);
	pos += scnprintf(buf + pos, bufsz - pos, "one_sided_disabled = %d\n",
			 cmd->one_sided_disabled);
	pos += scnprintf(buf + pos, bufsz - pos, "is_debug_mode = %d\n",
			 cmd->is_debug_mode);
	pos += scnprintf(buf + pos, bufsz - pos, "is_buf_required = %d\n",
			 cmd->is_buf_required);

	mutex_unlock(&mvm->mutex);

	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
}

static ssize_t iwl_dbgfs_tof_responder_params_write(struct ieee80211_vif *vif,
						    char *buf,
						    size_t count, loff_t *ppos)
{
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	int value, ret = 0;
	char *data;

	mutex_lock(&mvm->mutex);

	data = iwl_dbgfs_is_match("burst_period=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (!ret)
			mvm->tof_data.responder_cfg.burst_period =
							cpu_to_le16(value);
		goto out;
	}

	data = iwl_dbgfs_is_match("min_delta_ftm=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.min_delta_ftm = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("burst_duration=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.burst_duration = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("num_of_burst_exp=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.num_of_burst_exp = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("abort_responder=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.abort_responder = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("get_ch_est=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.get_ch_est = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("recv_sta_req_params=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.recv_sta_req_params = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("channel_num=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.channel_num = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("bandwidth=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.bandwidth = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("rate=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.rate = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("bssid=", buf);
	if (data) {
		u8 *mac = mvm->tof_data.responder_cfg.bssid;

		if (!mac_pton(data, mac)) {
			ret = -EINVAL;
			goto out;
		}
	}

	data = iwl_dbgfs_is_match("tsf_timer_offset_msecs=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.tsf_timer_offset_msecs =
							cpu_to_le16(value);
		goto out;
	}

	data = iwl_dbgfs_is_match("toa_offset=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.toa_offset =
							cpu_to_le16(value);
		goto out;
	}

	data = iwl_dbgfs_is_match("ctrl_ch_position=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.ctrl_ch_position = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("ftm_per_burst=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.ftm_per_burst = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("ftm_resp_ts_avail=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.ftm_resp_ts_avail = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("asap_mode=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.responder_cfg.asap_mode = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("send_responder_cfg=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0 && value) {
			ret = iwl_mvm_tof_responder_cmd(mvm, vif);
			goto out;
		}
	}

out:
	mutex_unlock(&mvm->mutex);

	return ret ?: count;
}

static ssize_t iwl_dbgfs_tof_responder_params_read(struct file *file,
						   char __user *user_buf,
						   size_t count, loff_t *ppos)
{
	struct ieee80211_vif *vif = file->private_data;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	char buf[256];
	int pos = 0;
	const size_t bufsz = sizeof(buf);
	struct iwl_tof_responder_config_cmd *cmd;

	cmd = &mvm->tof_data.responder_cfg;

	mutex_lock(&mvm->mutex);

	pos += scnprintf(buf + pos, bufsz - pos, "burst_period = %d\n",
			 le16_to_cpu(cmd->burst_period));
	pos += scnprintf(buf + pos, bufsz - pos, "burst_duration = %d\n",
			 cmd->burst_duration);
	pos += scnprintf(buf + pos, bufsz - pos, "bandwidth = %d\n",
			 cmd->bandwidth);
	pos += scnprintf(buf + pos, bufsz - pos, "channel_num = %d\n",
			 cmd->channel_num);
	pos += scnprintf(buf + pos, bufsz - pos, "ctrl_ch_position = 0x%x\n",
			 cmd->ctrl_ch_position);
	pos += scnprintf(buf + pos, bufsz - pos, "bssid = %pM\n",
			 cmd->bssid);
	pos += scnprintf(buf + pos, bufsz - pos, "min_delta_ftm = %d\n",
			 cmd->min_delta_ftm);
	pos += scnprintf(buf + pos, bufsz - pos, "num_of_burst_exp = %d\n",
			 cmd->num_of_burst_exp);
	pos += scnprintf(buf + pos, bufsz - pos, "rate = %d\n", cmd->rate);
	pos += scnprintf(buf + pos, bufsz - pos, "abort_responder = %d\n",
			 cmd->abort_responder);
	pos += scnprintf(buf + pos, bufsz - pos, "get_ch_est = %d\n",
			 cmd->get_ch_est);
	pos += scnprintf(buf + pos, bufsz - pos, "recv_sta_req_params = %d\n",
			 cmd->recv_sta_req_params);
	pos += scnprintf(buf + pos, bufsz - pos, "ftm_per_burst = %d\n",
			 cmd->ftm_per_burst);
	pos += scnprintf(buf + pos, bufsz - pos, "ftm_resp_ts_avail = %d\n",
			 cmd->ftm_resp_ts_avail);
	pos += scnprintf(buf + pos, bufsz - pos, "asap_mode = %d\n",
			 cmd->asap_mode);
	pos += scnprintf(buf + pos, bufsz - pos,
			 "tsf_timer_offset_msecs = %d\n",
			 le16_to_cpu(cmd->tsf_timer_offset_msecs));
	pos += scnprintf(buf + pos, bufsz - pos, "toa_offset = %d\n",
			 le16_to_cpu(cmd->toa_offset));

	mutex_unlock(&mvm->mutex);

	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
}

static ssize_t iwl_dbgfs_tof_range_request_write(struct ieee80211_vif *vif,
						 char *buf, size_t count,
						 loff_t *ppos)
{
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	int value, ret = 0;
	char *data;

	mutex_lock(&mvm->mutex);

	data = iwl_dbgfs_is_match("request_id=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.request_id = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("initiator=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.initiator = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("one_sided_los_disable=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.one_sided_los_disable = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("req_timeout=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.req_timeout = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("report_policy=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.report_policy = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("macaddr_random=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.macaddr_random = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("num_of_ap=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req.num_of_ap = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("macaddr_template=", buf);
	if (data) {
		u8 mac[ETH_ALEN];

		if (!mac_pton(data, mac)) {
			ret = -EINVAL;
			goto out;
		}
		memcpy(mvm->tof_data.range_req.macaddr_template, mac, ETH_ALEN);
	}

	data = iwl_dbgfs_is_match("macaddr_mask=", buf);
	if (data) {
		u8 mac[ETH_ALEN];

		if (!mac_pton(data, mac)) {
			ret = -EINVAL;
			goto out;
		}
		memcpy(mvm->tof_data.range_req.macaddr_mask, mac, ETH_ALEN);
	}

	data = iwl_dbgfs_is_match("ap=", buf);
	if (data) {
		struct iwl_tof_range_req_ap_entry ap;
		int size = sizeof(struct iwl_tof_range_req_ap_entry);
		u16 burst_period;
		u8 *mac = ap.bssid;
		int i;

		if (sscanf(data, "%d %hhd %hhx %hhx"
			   "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx"
			   "%hhx %hhx %hx"
			   "%hhx %hhx %x"
			   "%hhx %hhx %hhx %hhx",
			   &i, &ap.channel_num, &ap.bandwidth,
			   &ap.ctrl_ch_position,
			   mac, mac + 1, mac + 2, mac + 3, mac + 4, mac + 5,
			   &ap.measure_type, &ap.num_of_bursts,
			   &burst_period,
			   &ap.samples_per_burst, &ap.retries_per_sample,
			   &ap.tsf_delta, &ap.location_req, &ap.asap_mode,
			   &ap.enable_dyn_ack, &ap.rssi) != 20) {
			ret = -EINVAL;
			goto out;
		}
		if (i > IWL_MVM_TOF_MAX_APS) {
			IWL_ERR(mvm, "Invalid AP index %d\n", i);
			ret = -EINVAL;
			goto out;
		}

		ap.burst_period = cpu_to_le16(burst_period);

		memcpy(&mvm->tof_data.range_req.ap[i], &ap, size);
		goto out;
	}

	data = iwl_dbgfs_is_match("send_range_request=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0 && value) {
			ret = iwl_mvm_tof_range_request_cmd(mvm, vif);
			goto out;
		}
	}

out:
	mutex_unlock(&mvm->mutex);
	return ret ?: count;
}

static ssize_t iwl_dbgfs_tof_range_request_read(struct file *file,
						char __user *user_buf,
						size_t count, loff_t *ppos)
{
	struct ieee80211_vif *vif = file->private_data;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	char buf[512];
	int pos = 0;
	const size_t bufsz = sizeof(buf);
	struct iwl_tof_range_req_cmd *cmd;
	int i;

	cmd = &mvm->tof_data.range_req;

	mutex_lock(&mvm->mutex);

	pos += scnprintf(buf + pos, bufsz - pos, "request_id= %d\n",
			 cmd->request_id);
	pos += scnprintf(buf + pos, bufsz - pos, "initiator= %d\n",
			 cmd->initiator);
	pos += scnprintf(buf + pos, bufsz - pos, "one_sided_los_disable = %d\n",
			 cmd->one_sided_los_disable);
	pos += scnprintf(buf + pos, bufsz - pos, "req_timeout= %d\n",
			 cmd->req_timeout);
	pos += scnprintf(buf + pos, bufsz - pos, "report_policy= %d\n",
			 cmd->report_policy);
	pos += scnprintf(buf + pos, bufsz - pos, "macaddr_random= %d\n",
			 cmd->macaddr_random);
	pos += scnprintf(buf + pos, bufsz - pos, "macaddr_template= %pM\n",
			 cmd->macaddr_template);
	pos += scnprintf(buf + pos, bufsz - pos, "macaddr_mask= %pM\n",
			 cmd->macaddr_mask);
	pos += scnprintf(buf + pos, bufsz - pos, "num_of_ap= %d\n",
			 cmd->num_of_ap);
	for (i = 0; i < cmd->num_of_ap; i++) {
		struct iwl_tof_range_req_ap_entry *ap = &cmd->ap[i];

		pos += scnprintf(buf + pos, bufsz - pos,
				"ap %.2d: channel_num=%hhx bw=%hhx"
				" control=%hhx bssid=%pM type=%hhx"
				" num_of_bursts=%hhx burst_period=%hx ftm=%hhx"
				" retries=%hhx tsf_delta=%x location_req=%hhx "
				" asap=%hhx enable=%hhx rssi=%hhx\n",
				i, ap->channel_num, ap->bandwidth,
				ap->ctrl_ch_position, ap->bssid,
				ap->measure_type, ap->num_of_bursts,
				ap->burst_period, ap->samples_per_burst,
				ap->retries_per_sample, ap->tsf_delta,
				ap->location_req, ap->asap_mode,
				ap->enable_dyn_ack, ap->rssi);
	}

	mutex_unlock(&mvm->mutex);

	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
}

static ssize_t iwl_dbgfs_tof_range_req_ext_write(struct ieee80211_vif *vif,
						 char *buf,
						 size_t count, loff_t *ppos)
{
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	int value, ret = 0;
	char *data;

	mutex_lock(&mvm->mutex);

	data = iwl_dbgfs_is_match("tsf_timer_offset_msec=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req_ext.tsf_timer_offset_msec =
							cpu_to_le16(value);
		goto out;
	}

	data = iwl_dbgfs_is_match("min_delta_ftm=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req_ext.min_delta_ftm = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("ftm_format_and_bw20M=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req_ext.ftm_format_and_bw20M =
									value;
		goto out;
	}

	data = iwl_dbgfs_is_match("ftm_format_and_bw40M=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req_ext.ftm_format_and_bw40M =
									value;
		goto out;
	}

	data = iwl_dbgfs_is_match("ftm_format_and_bw80M=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.range_req_ext.ftm_format_and_bw80M =
									value;
		goto out;
	}

	data = iwl_dbgfs_is_match("send_range_req_ext=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0 && value) {
			ret = iwl_mvm_tof_range_request_ext_cmd(mvm, vif);
			goto out;
		}
	}

out:
	mutex_unlock(&mvm->mutex);
	return ret ?: count;
}

static ssize_t iwl_dbgfs_tof_range_req_ext_read(struct file *file,
						char __user *user_buf,
						size_t count, loff_t *ppos)
{
	struct ieee80211_vif *vif = file->private_data;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	char buf[256];
	int pos = 0;
	const size_t bufsz = sizeof(buf);
	struct iwl_tof_range_req_ext_cmd *cmd;

	cmd = &mvm->tof_data.range_req_ext;

	mutex_lock(&mvm->mutex);

	pos += scnprintf(buf + pos, bufsz - pos,
			 "tsf_timer_offset_msec = %hx\n",
			 cmd->tsf_timer_offset_msec);
	pos += scnprintf(buf + pos, bufsz - pos, "min_delta_ftm = %hhx\n",
			 cmd->min_delta_ftm);
	pos += scnprintf(buf + pos, bufsz - pos,
			 "ftm_format_and_bw20M = %hhx\n",
			 cmd->ftm_format_and_bw20M);
	pos += scnprintf(buf + pos, bufsz - pos,
			 "ftm_format_and_bw40M = %hhx\n",
			 cmd->ftm_format_and_bw40M);
	pos += scnprintf(buf + pos, bufsz - pos,
			 "ftm_format_and_bw80M = %hhx\n",
			 cmd->ftm_format_and_bw80M);

	mutex_unlock(&mvm->mutex);
	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
}

static ssize_t iwl_dbgfs_tof_range_abort_write(struct ieee80211_vif *vif,
					       char *buf,
					       size_t count, loff_t *ppos)
{
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	int value, ret = 0;
	int abort_id;
	char *data;

	mutex_lock(&mvm->mutex);

	data = iwl_dbgfs_is_match("abort_id=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0)
			mvm->tof_data.last_abort_id = value;
		goto out;
	}

	data = iwl_dbgfs_is_match("send_range_abort=", buf);
	if (data) {
		ret = kstrtou32(data, 10, &value);
		if (ret == 0 && value) {
			abort_id = mvm->tof_data.last_abort_id;
			ret = iwl_mvm_tof_range_abort_cmd(mvm, abort_id);
			goto out;
		}
	}

out:
	mutex_unlock(&mvm->mutex);
	return ret ?: count;
}

static ssize_t iwl_dbgfs_tof_range_abort_read(struct file *file,
					      char __user *user_buf,
					      size_t count, loff_t *ppos)
{
	struct ieee80211_vif *vif = file->private_data;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	char buf[32];
	int pos = 0;
	const size_t bufsz = sizeof(buf);
	int last_abort_id;

	mutex_lock(&mvm->mutex);
	last_abort_id = mvm->tof_data.last_abort_id;
	mutex_unlock(&mvm->mutex);

	pos += scnprintf(buf + pos, bufsz - pos, "last_abort_id = %d\n",
			 last_abort_id);
	return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
}

static ssize_t iwl_dbgfs_tof_range_response_read(struct file *file,
						 char __user *user_buf,
						 size_t count, loff_t *ppos)
{
	struct ieee80211_vif *vif = file->private_data;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	struct iwl_mvm *mvm = mvmvif->mvm;
	char *buf;
	int pos = 0;
	const size_t bufsz = sizeof(struct iwl_tof_range_rsp_ntfy) + 256;
	struct iwl_tof_range_rsp_ntfy *cmd;
	int i, ret;

	buf = kzalloc(bufsz, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	mutex_lock(&mvm->mutex);
	cmd = &mvm->tof_data.range_resp;

	pos += scnprintf(buf + pos, bufsz - pos, "request_id = %d\n",
			 cmd->request_id);
	pos += scnprintf(buf + pos, bufsz - pos, "status = %d\n",
			 cmd->request_status);
	pos += scnprintf(buf + pos, bufsz - pos, "last_in_batch = %d\n",
			 cmd->last_in_batch);
	pos += scnprintf(buf + pos, bufsz - pos, "num_of_aps = %d\n",
			 cmd->num_of_aps);
	for (i = 0; i < cmd->num_of_aps; i++) {
		struct iwl_tof_range_rsp_ap_entry_ntfy *ap = &cmd->ap[i];

		pos += scnprintf(buf + pos, bufsz - pos,
				"ap %.2d: bssid=%pM status=%hhx bw=%hhx"
				" rtt=%x rtt_var=%x rtt_spread=%x"
				" rssi=%hhx  rssi_spread=%hhx"
				" range=%x range_var=%x"
				" time_stamp=%x\n",
				i, ap->bssid, ap->measure_status,
				ap->measure_bw,
				ap->rtt, ap->rtt_variance, ap->rtt_spread,
				ap->rssi, ap->rssi_spread, ap->range,
				ap->range_variance, ap->timestamp);
	}
	mutex_unlock(&mvm->mutex);

	ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos);
	kfree(buf);
	return ret;
}

static ssize_t iwl_dbgfs_low_latency_write(struct ieee80211_vif *vif, char *buf,
					   size_t count, loff_t *ppos)
{
@@ -628,6 +1354,12 @@ MVM_DEBUGFS_READ_WRITE_FILE_OPS(bf_params, 256);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(low_latency, 10);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(uapsd_misbehaving, 20);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(rx_phyinfo, 10);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(tof_enable, 32);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(tof_range_request, 512);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(tof_range_req_ext, 32);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(tof_range_abort, 32);
MVM_DEBUGFS_READ_FILE_OPS(tof_range_response);
MVM_DEBUGFS_READ_WRITE_FILE_OPS(tof_responder_params, 32);

void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
{
@@ -671,6 +1403,25 @@ void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
		MVM_DEBUGFS_ADD_FILE_VIF(bf_params, mvmvif->dbgfs_dir,
					 S_IRUSR | S_IWUSR);

	if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TOF_SUPPORT) &&
	    !vif->p2p && (vif->type != NL80211_IFTYPE_P2P_DEVICE)) {
		if (IWL_MVM_TOF_IS_RESPONDER && vif->type == NL80211_IFTYPE_AP)
			MVM_DEBUGFS_ADD_FILE_VIF(tof_responder_params,
						 mvmvif->dbgfs_dir,
						 S_IRUSR | S_IWUSR);

		MVM_DEBUGFS_ADD_FILE_VIF(tof_range_request, mvmvif->dbgfs_dir,
					 S_IRUSR | S_IWUSR);
		MVM_DEBUGFS_ADD_FILE_VIF(tof_range_req_ext, mvmvif->dbgfs_dir,
					 S_IRUSR | S_IWUSR);
		MVM_DEBUGFS_ADD_FILE_VIF(tof_enable, mvmvif->dbgfs_dir,
					 S_IRUSR | S_IWUSR);
		MVM_DEBUGFS_ADD_FILE_VIF(tof_range_abort, mvmvif->dbgfs_dir,
					 S_IRUSR | S_IWUSR);
		MVM_DEBUGFS_ADD_FILE_VIF(tof_range_response, mvmvif->dbgfs_dir,
					 S_IRUSR);
	}

	/*
	 * Create symlink for convenience pointing to interface specific
	 * debugfs entries for the driver. For example, under
Loading