summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/wireless/intel/iwlwifi/fw/api/tx.h11
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/constants.h4
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/d3.c5
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/fw.c7
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mac80211.c2
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mld.c13
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/mld.h6
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/ptp.c12
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tx.c130
-rw-r--r--drivers/net/wireless/intel/iwlwifi/mld/tx.h36
10 files changed, 209 insertions, 17 deletions
diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h
index 31d3336726b4..9d9eb320db7b 100644
--- a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h
+++ b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
#ifndef __iwl_fw_api_tx_h__
@@ -292,6 +292,9 @@ struct iwl_tx_cmd_v9 {
* @dram_info: FW internal DRAM storage
* @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is
* cleared. Combination of RATE_MCS_*; format depends on version
+ * @driver_timestamp: gp2 time, in microseconds, assigned before queueing the
+ * MPDU. Firmware uses it to detect frames blocked for too long.
+ * Zero means no timestamp. Used from TX_CMD_API_S_VER_12.
* @reserved: reserved
* @hdr: 802.11 header
*/
@@ -301,10 +304,12 @@ struct iwl_tx_cmd {
__le32 offload_assist;
struct iwl_dram_sec_info dram_info;
__le32 rate_n_flags;
- u8 reserved[8];
+ __le32 driver_timestamp;
+ u8 reserved[4];
struct ieee80211_hdr hdr[];
} __packed; /* TX_CMD_API_S_VER_10,
- * TX_CMD_API_S_VER_11
+ * TX_CMD_API_S_VER_11,
+ * TX_CMD_API_S_VER_12
*/
/*
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/constants.h b/drivers/net/wireless/intel/iwlwifi/mld/constants.h
index 890abcab3837..bfd5398510ee 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/constants.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/constants.h
@@ -39,6 +39,10 @@
#define IWL_MLD_TPT_COUNT_WINDOW (5 * HZ)
+#define IWL_MLD_TX_GP2_VALID_PERIOD (10 * HZ)
+#define IWL_MLD_TX_GP2_REFRESH_MARGIN (5 * HZ)
+#define IWL_MLD_TX_GP2_RETRY_PERIOD HZ
+
#define IWL_MLD_DIS_RANDOM_FW_ID false
#define IWL_MLD_D3_DEBUG false
#define IWL_MLD_NON_TRANSMITTING_AP false
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c
index 6d8ede336d59..7a7d07a7914d 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c
@@ -5,6 +5,7 @@
#include "mld.h"
#include "d3.h"
+#include "tx.h"
#include "power.h"
#include "hcmd.h"
#include "iface.h"
@@ -1610,6 +1611,8 @@ int iwl_mld_no_wowlan_suspend(struct iwl_mld *mld)
iwl_mld_low_latency_stop(mld);
+ iwl_mld_tx_gp2_stop(mld);
+
ret = iwl_mld_update_device_power(mld, true);
if (ret) {
IWL_ERR(mld,
@@ -1663,6 +1666,8 @@ int iwl_mld_no_wowlan_resume(struct iwl_mld *mld)
iwl_mld_low_latency_restart(mld);
+ iwl_mld_tx_gp2_start(mld);
+
return iwl_mld_update_device_power(mld, false);
}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/fw.c b/drivers/net/wireless/intel/iwlwifi/mld/fw.c
index e5866d89176d..7891afd556a8 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/fw.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/fw.c
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include "mld.h"
+#include "tx.h"
#include "fw/api/alive.h"
#include "fw/api/scan.h"
@@ -355,6 +356,8 @@ void iwl_mld_stop_fw(struct iwl_mld *mld)
{
lockdep_assert_wiphy(mld->wiphy);
+ iwl_mld_tx_gp2_stop(mld);
+
iwl_abort_notification_waits(&mld->notif_wait);
iwl_fw_dbg_stop_sync(&mld->fwrt);
@@ -542,6 +545,8 @@ int iwl_mld_start_fw(struct iwl_mld *mld)
if (ret)
goto error;
+ iwl_mld_tx_gp2_start(mld);
+
return 0;
error:
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index 0b121114d47c..e05bdbd3e1a9 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -2168,6 +2168,8 @@ static int iwl_mld_resume(struct ieee80211_hw *hw)
iwl_mld_low_latency_restart(mld);
+ iwl_mld_tx_gp2_start(mld);
+
return 0;
}
#endif
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.c b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
index b7b1e3686336..95d6f36b9fd9 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.c
@@ -35,6 +35,17 @@ MODULE_IMPORT_NS("IWLWIFI");
static const struct iwl_op_mode_ops iwl_mld_ops;
+int iwl_mld_get_systime(struct iwl_mld *mld, u32 *gp2)
+{
+ *gp2 = iwl_read_prph(mld->trans,
+ mld->trans->mac_cfg->base->gp2_reg_addr);
+
+ if (*gp2 == 0x5a5a5a5a)
+ return -EINVAL;
+
+ return 0;
+}
+
static int __init iwl_mld_init(void)
{
int ret = iwl_opmode_register("iwlmld", &iwl_mld_ops);
@@ -425,6 +436,8 @@ iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_rf_cfg *cfg,
/* Configure transport layer with the opmode specific params */
iwl_mld_configure_trans(op_mode);
+ iwl_mld_tx_gp2_init(mld);
+
/* needed for regulatory init */
rtnl_lock();
/* Needed for sending commands */
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mld.h b/drivers/net/wireless/intel/iwlwifi/mld/mld.h
index 37f41b504b54..a9e65b216fd3 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mld.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mld.h
@@ -29,6 +29,7 @@
#include "notif.h"
#include "scan.h"
#include "rx.h"
+#include "tx.h"
#include "thermal.h"
#include "low_latency.h"
#include "constants.h"
@@ -196,6 +197,7 @@
* being IBSS manager for that time and needing to respond to probe
* requests
* @ptp_data: data of the PTP clock
+ * @tx_gp2: gp2 timestamping state for TX commands
* @time_sync: time sync data.
* @ftm_initiator: FTM initiator data
* @nan_device_vif: points to the NAN device vif if exists
@@ -306,6 +308,8 @@ struct iwl_mld {
struct ptp_data ptp_data;
+ struct iwl_mld_tx_gp2 tx_gp2;
+
struct iwl_mld_time_sync_data __rcu *time_sync;
struct ftm_initiator_data ftm_initiator;
@@ -572,6 +576,8 @@ iwl_mld_fw_id_to_link_conf(struct iwl_mld *mld, u8 fw_link_id)
#define MSEC_TO_TU(_msec) ((_msec) * 1000 / 1024)
+int iwl_mld_get_systime(struct iwl_mld *mld, u32 *gp2);
+
void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c
index 1ac85d6ce1d7..9f7d17dbdee1 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025 - 2026 Intel Corporation
*/
#include "mld.h"
@@ -19,16 +19,6 @@
#define IWL_PTP_WRAP_TIME (3600 * HZ)
#define IWL_PTP_WRAP_THRESHOLD_USEC (5000)
-static int iwl_mld_get_systime(struct iwl_mld *mld, u32 *gp2)
-{
- *gp2 = iwl_read_prph(mld->trans, mld->trans->mac_cfg->base->gp2_reg_addr);
-
- if (*gp2 == 0x5a5a5a5a)
- return -EINVAL;
-
- return 0;
-}
-
static void iwl_mld_ptp_update_new_read(struct iwl_mld *mld, u32 gp2)
{
IWL_DEBUG_PTP(mld, "PTP: last_gp2=%u, new gp2 read=%u\n",
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c
index 2df6643a5aa8..aac7d43bffd1 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c
@@ -2,6 +2,7 @@
/*
* Copyright (C) 2024 - 2026 Intel Corporation
*/
+#include <linux/timekeeping.h>
#include <net/ip.h>
#include "tx.h"
@@ -20,6 +21,132 @@
#define MAX_ANT_NUM 2
+static u32 iwl_mld_tx_gp2_host_us(void)
+{
+ return div_u64(ktime_get_boottime_ns(), NSEC_PER_USEC);
+}
+
+/* Returns the gp2 for the TX command, or 0 to leave the frame unstamped.
+ * Sets @refresh when the estimate expired or is about to.
+ */
+static u32
+iwl_mld_tx_gp2_from_est(u32 delta_us, unsigned long valid_until, u32 host_us,
+ unsigned long now, bool *refresh)
+{
+ u32 gp2;
+
+ *refresh = time_after_eq(now, valid_until -
+ IWL_MLD_TX_GP2_REFRESH_MARGIN);
+
+ if (time_after_eq(now, valid_until))
+ return 0;
+
+ gp2 = host_us + delta_us;
+
+ /* 0 means no timestamp; use 1 for a valid wrapped value. */
+ return gp2 ?: 1;
+}
+
+static u32
+iwl_mld_tx_gp2_timestamp(struct iwl_mld *mld)
+{
+ unsigned long valid_until =
+ atomic_long_read_acquire(&mld->tx_gp2.valid_until);
+ bool refresh = false;
+ u32 gp2;
+
+ if (!valid_until)
+ return 0;
+
+ gp2 = iwl_mld_tx_gp2_from_est(atomic_read(&mld->tx_gp2.delta_us),
+ valid_until, iwl_mld_tx_gp2_host_us(),
+ jiffies, &refresh);
+
+ if (refresh)
+ wiphy_delayed_work_queue(mld->wiphy, &mld->tx_gp2.wk, 0);
+
+ return gp2;
+}
+
+static void iwl_mld_tx_gp2_refresh(struct iwl_mld *mld)
+{
+ unsigned long valid_until, delay;
+ u32 gp2, host_us;
+
+ lockdep_assert_wiphy(mld->wiphy);
+
+ if (!mld->fw_status.running)
+ return;
+
+#ifdef CONFIG_PM_SLEEP
+ if (mld->fw_status.in_d3)
+ return;
+#endif
+
+ if (iwl_mld_get_systime(mld, &gp2))
+ goto retry;
+
+ host_us = iwl_mld_tx_gp2_host_us();
+ valid_until = jiffies + IWL_MLD_TX_GP2_VALID_PERIOD;
+
+ /* Keep arithmetic in u32 to match firmware wraparound semantics. */
+ atomic_set(&mld->tx_gp2.delta_us, gp2 - host_us);
+ atomic_long_set_release(&mld->tx_gp2.valid_until, valid_until ?: 1);
+
+ IWL_DEBUG_TX(mld, "gp2 sync: gp2=%u host_us=%u delta_us=%u\n",
+ gp2, host_us, gp2 - host_us);
+ return;
+
+retry:
+ /* Stop new TX readers from requesting immediate retries. */
+ atomic_long_set(&mld->tx_gp2.valid_until, 0);
+ delay = round_jiffies_relative(IWL_MLD_TX_GP2_RETRY_PERIOD);
+ wiphy_delayed_work_queue(mld->wiphy, &mld->tx_gp2.wk, delay);
+}
+
+static void iwl_mld_tx_gp2_wk(struct wiphy *wiphy, struct wiphy_work *wk)
+{
+ struct iwl_mld *mld = container_of(wk, struct iwl_mld, tx_gp2.wk.work);
+ unsigned long valid_until = atomic_long_read(&mld->tx_gp2.valid_until);
+
+ /* TX may have queued this work using an older estimate. */
+ if (valid_until &&
+ time_before(jiffies, valid_until - IWL_MLD_TX_GP2_REFRESH_MARGIN))
+ return;
+
+ iwl_mld_tx_gp2_refresh(mld);
+}
+
+void iwl_mld_tx_gp2_init(struct iwl_mld *mld)
+{
+ atomic_set(&mld->tx_gp2.delta_us, 0);
+ atomic_long_set(&mld->tx_gp2.valid_until, 0);
+ wiphy_delayed_work_init(&mld->tx_gp2.wk, iwl_mld_tx_gp2_wk);
+}
+
+void iwl_mld_tx_gp2_start(struct iwl_mld *mld)
+{
+ lockdep_assert_wiphy(mld->wiphy);
+
+ if (iwl_fw_lookup_cmd_ver(mld->fw, TX_CMD, 0) < 12)
+ return;
+
+ iwl_mld_tx_gp2_refresh(mld);
+}
+
+/* TX is quiesced before stopping the refresh work. */
+void iwl_mld_tx_gp2_stop(struct iwl_mld *mld)
+{
+ lockdep_assert_wiphy(mld->wiphy);
+
+ if (iwl_fw_lookup_cmd_ver(mld->fw, TX_CMD, 0) < 12)
+ return;
+
+ wiphy_delayed_work_cancel(mld->wiphy, &mld->tx_gp2.wk);
+
+ atomic_long_set(&mld->tx_gp2.valid_until, 0);
+}
+
/* Toggles between TX antennas. Receives the bitmask of valid TX antennas and
* the *index* used for the last TX, and returns the next valid *index* to use.
* In order to set it in the tx_cmd, must do BIT(idx).
@@ -588,6 +715,9 @@ iwl_mld_fill_tx_cmd(struct iwl_mld *mld, struct sk_buff *skb,
tx_cmd->flags = cpu_to_le16(flags);
tx_cmd->rate_n_flags = rate_n_flags;
+
+ tx_cmd->driver_timestamp =
+ cpu_to_le32(iwl_mld_tx_gp2_timestamp(mld));
}
/* Caller of this need to check that info->control.vif is not NULL */
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.h b/drivers/net/wireless/intel/iwlwifi/mld/tx.h
index 520f15f9d33c..0a9a2e4abcab 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.h
@@ -1,11 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2024 Intel Corporation
+ * Copyright (C) 2024, 2026 Intel Corporation
*/
#ifndef __iwl_mld_tx_h__
#define __iwl_mld_tx_h__
-#include "mld.h"
+#include <linux/atomic.h>
+#include <net/mac80211.h>
+
+struct iwl_mld;
+struct iwl_rx_packet;
#define IWL_MLD_INVALID_QUEUE 0xFFFF
#define IWL_MLD_INVALID_DROP_TX 0xFFFE
@@ -74,4 +78,32 @@ u8 iwl_mld_get_lowest_rate(struct iwl_mld *mld,
void iwl_mld_tx_skb(struct iwl_mld *mld, struct sk_buff *skb,
struct ieee80211_txq *txq);
+/**
+ * struct iwl_mld_tx_gp2 - gp2 timestamping state for TX commands
+ *
+ * Reading gp2 directly in the TX path is too costly. Instead, TX lazily
+ * schedules @wk to refresh gp2 in process context and adds @delta_us to the
+ * host clock to timestamp queued frames.
+ *
+ * A zero @valid_until leaves frames unstamped, including on older firmware
+ * without TX_CMD v12. A deadline that wraps to zero is published as one.
+ * Failed refreshes clear @valid_until, leaving @delta_us for old readers, and
+ * schedule a retry with %IWL_MLD_TX_GP2_RETRY_PERIOD rounded for timer
+ * coalescing, even without further TX traffic. Old readers may retry sooner.
+ *
+ * @delta_us: gp2 minus host time, in microseconds, modulo 2^32;
+ * valid while @valid_until is nonzero and has not expired
+ * @valid_until: jiffies deadline for the offset, or zero when disabled
+ * @wk: worker that refreshes the offset and retries failures
+ */
+struct iwl_mld_tx_gp2 {
+ atomic_t delta_us;
+ atomic_long_t valid_until;
+ struct wiphy_delayed_work wk;
+};
+
+void iwl_mld_tx_gp2_init(struct iwl_mld *mld);
+void iwl_mld_tx_gp2_start(struct iwl_mld *mld);
+void iwl_mld_tx_gp2_stop(struct iwl_mld *mld);
+
#endif /* __iwl_mld_tx_h__ */