diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/time_sync.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/time_sync.c | 240 | 
1 files changed, 240 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/time_sync.c b/sys/contrib/dev/iwlwifi/mld/time_sync.c new file mode 100644 index 000000000000..50799f9bfccb --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/time_sync.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2025 Intel Corporation + */ + +#include "mld.h" +#include "hcmd.h" +#include "ptp.h" +#include "time_sync.h" +#include <linux/ieee80211.h> + +static int iwl_mld_init_time_sync(struct iwl_mld *mld, u32 protocols, +				  const u8 *addr) +{ +	struct iwl_mld_time_sync_data *time_sync = kzalloc(sizeof(*time_sync), +							   GFP_KERNEL); + +	if (!time_sync) +		return -ENOMEM; + +	time_sync->active_protocols = protocols; +	ether_addr_copy(time_sync->peer_addr, addr); +	skb_queue_head_init(&time_sync->frame_list); +	rcu_assign_pointer(mld->time_sync, time_sync); + +	return 0; +} + +int iwl_mld_time_sync_fw_config(struct iwl_mld *mld) +{ +	struct iwl_time_sync_cfg_cmd cmd = {}; +	struct iwl_mld_time_sync_data *time_sync; +	int err; + +	time_sync = wiphy_dereference(mld->wiphy, mld->time_sync); +	if (!time_sync) +		return -EINVAL; + +	cmd.protocols = cpu_to_le32(time_sync->active_protocols); +	ether_addr_copy(cmd.peer_addr, time_sync->peer_addr); + +	err = iwl_mld_send_cmd_pdu(mld, +				   WIDE_ID(DATA_PATH_GROUP, +					   WNM_80211V_TIMING_MEASUREMENT_CONFIG_CMD), +				   &cmd); +	if (err) +		IWL_ERR(mld, "Failed to send time sync cfg cmd: %d\n", err); + +	return err; +} + +int iwl_mld_time_sync_config(struct iwl_mld *mld, const u8 *addr, u32 protocols) +{ +	struct iwl_mld_time_sync_data *time_sync; +	int err; + +	time_sync = wiphy_dereference(mld->wiphy, mld->time_sync); + +	/* The fw only supports one peer. We do allow reconfiguration of the +	 * same peer for cases of fw reset etc. +	 */ +	if (time_sync && time_sync->active_protocols && +	    !ether_addr_equal(addr, time_sync->peer_addr)) { +		IWL_DEBUG_INFO(mld, "Time sync: reject config for peer: %pM\n", +			       addr); +		return -ENOBUFS; +	} + +	if (protocols & ~(IWL_TIME_SYNC_PROTOCOL_TM | +			  IWL_TIME_SYNC_PROTOCOL_FTM)) +		return -EINVAL; + +	IWL_DEBUG_INFO(mld, "Time sync: set peer addr=%pM\n", addr); + +	iwl_mld_deinit_time_sync(mld); +	err = iwl_mld_init_time_sync(mld, protocols, addr); +	if (err) +		return err; + +	err = iwl_mld_time_sync_fw_config(mld); +	return err; +} + +void iwl_mld_deinit_time_sync(struct iwl_mld *mld) +{ +	struct iwl_mld_time_sync_data *time_sync = +		wiphy_dereference(mld->wiphy, mld->time_sync); + +	if (!time_sync) +		return; + +	RCU_INIT_POINTER(mld->time_sync, NULL); +	skb_queue_purge(&time_sync->frame_list); +	kfree_rcu(time_sync, rcu_head); +} + +bool iwl_mld_time_sync_frame(struct iwl_mld *mld, struct sk_buff *skb, u8 *addr) +{ +	struct iwl_mld_time_sync_data *time_sync; + +	rcu_read_lock(); +	time_sync = rcu_dereference(mld->time_sync); +	if (time_sync && ether_addr_equal(time_sync->peer_addr, addr) && +	    (ieee80211_is_timing_measurement(skb) || ieee80211_is_ftm(skb))) { +		skb_queue_tail(&time_sync->frame_list, skb); +		rcu_read_unlock(); +		return true; +	} +	rcu_read_unlock(); + +	return false; +} + +static bool iwl_mld_is_skb_match(struct sk_buff *skb, u8 *addr, u8 dialog_token) +{ +	struct ieee80211_mgmt *mgmt = (void *)skb->data; +	u8 skb_dialog_token; + +	if (ieee80211_is_timing_measurement(skb)) +		skb_dialog_token = mgmt->u.action.u.wnm_timing_msr.dialog_token; +	else +		skb_dialog_token = mgmt->u.action.u.ftm.dialog_token; + +	if ((ether_addr_equal(mgmt->sa, addr) || +	     ether_addr_equal(mgmt->da, addr)) && +	    skb_dialog_token == dialog_token) +		return true; + +	return false; +} + +static struct sk_buff *iwl_mld_time_sync_find_skb(struct iwl_mld *mld, u8 *addr, +						  u8 dialog_token) +{ +	struct iwl_mld_time_sync_data *time_sync; +	struct sk_buff *skb; + +	rcu_read_lock(); + +	time_sync = rcu_dereference(mld->time_sync); +	if (IWL_FW_CHECK(mld, !time_sync, +			 "Time sync notification but time sync is not initialized\n")) { +		rcu_read_unlock(); +		return NULL; +	} + +	/* The notifications are expected to arrive in the same order of the +	 * frames. If the incoming notification doesn't match the first SKB +	 * in the queue, it means there was no time sync notification for this +	 * SKB and it can be dropped. +	 */ +	while ((skb = skb_dequeue(&time_sync->frame_list))) { +		if (iwl_mld_is_skb_match(skb, addr, dialog_token)) +			break; + +		kfree_skb(skb); +		skb = NULL; +		IWL_DEBUG_DROP(mld, +			       "Time sync: drop SKB without matching notification\n"); +	} +	rcu_read_unlock(); + +	return skb; +} + +static u64 iwl_mld_get_64_bit(__le32 high, __le32 low) +{ +	return ((u64)le32_to_cpu(high) << 32) | le32_to_cpu(low); +} + +void iwl_mld_handle_time_msmt_notif(struct iwl_mld *mld, +				    struct iwl_rx_packet *pkt) +{ +	struct ptp_data *data = &mld->ptp_data; +	struct iwl_time_msmt_notify *notif = (void *)pkt->data; +	struct ieee80211_rx_status *rx_status; +	struct skb_shared_hwtstamps *shwt; +	u64 ts_10ns; +	struct sk_buff *skb = +		iwl_mld_time_sync_find_skb(mld, notif->peer_addr, +					   le32_to_cpu(notif->dialog_token)); +	u64 adj_time; + +	if (IWL_FW_CHECK(mld, !skb, "Time sync event but no pending skb\n")) +		return; + +	spin_lock_bh(&data->lock); +	ts_10ns = iwl_mld_get_64_bit(notif->t2_hi, notif->t2_lo); +	adj_time = iwl_mld_ptp_get_adj_time(mld, ts_10ns * 10); +	shwt = skb_hwtstamps(skb); +	shwt->hwtstamp = ktime_set(0, adj_time); + +	ts_10ns = iwl_mld_get_64_bit(notif->t3_hi, notif->t3_lo); +	adj_time = iwl_mld_ptp_get_adj_time(mld, ts_10ns * 10); +	rx_status = IEEE80211_SKB_RXCB(skb); +	rx_status->ack_tx_hwtstamp = ktime_set(0, adj_time); +	spin_unlock_bh(&data->lock); + +	IWL_DEBUG_INFO(mld, +		       "Time sync: RX event - report frame t2=%llu t3=%llu\n", +		       ktime_to_ns(shwt->hwtstamp), +		       ktime_to_ns(rx_status->ack_tx_hwtstamp)); +	ieee80211_rx_napi(mld->hw, NULL, skb, NULL); +} + +void iwl_mld_handle_time_sync_confirm_notif(struct iwl_mld *mld, +					    struct iwl_rx_packet *pkt) +{ +	struct ptp_data *data = &mld->ptp_data; +	struct iwl_time_msmt_cfm_notify *notif = (void *)pkt->data; +	struct ieee80211_tx_status status = {}; +	struct skb_shared_hwtstamps *shwt; +	u64 ts_10ns, adj_time; + +	status.skb = +		iwl_mld_time_sync_find_skb(mld, notif->peer_addr, +					   le32_to_cpu(notif->dialog_token)); + +	if (IWL_FW_CHECK(mld, !status.skb, +			 "Time sync confirm but no pending skb\n")) +		return; + +	spin_lock_bh(&data->lock); +	ts_10ns = iwl_mld_get_64_bit(notif->t1_hi, notif->t1_lo); +	adj_time = iwl_mld_ptp_get_adj_time(mld, ts_10ns * 10); +	shwt = skb_hwtstamps(status.skb); +	shwt->hwtstamp = ktime_set(0, adj_time); + +	ts_10ns = iwl_mld_get_64_bit(notif->t4_hi, notif->t4_lo); +	adj_time = iwl_mld_ptp_get_adj_time(mld, ts_10ns * 10); +	status.info = IEEE80211_SKB_CB(status.skb); +	status.ack_hwtstamp = ktime_set(0, adj_time); +	spin_unlock_bh(&data->lock); + +	IWL_DEBUG_INFO(mld, +		       "Time sync: TX event - report frame t1=%llu t4=%llu\n", +		       ktime_to_ns(shwt->hwtstamp), +		       ktime_to_ns(status.ack_hwtstamp)); +	ieee80211_tx_status_ext(mld->hw, &status); +} | 
