// 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 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); }