diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 353488ab94a2..a369f0d7087d 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7599,4 +7599,17 @@ int ieee80211_emulate_switch_vif_chanctx(struct ieee80211_hw *hw,
 					 int n_vifs,
 					 enum ieee80211_chanctx_switch_mode mode);
 
+/**
+ * ieee80211_critical_update - update critical params for each link
+ * @vif: the specified virtual interface
+ * @link_id: the link ID for MLO, otherwise 0
+ * @critical_flag: critical update information
+ * @bpcc: Bss parameter change count value
+ *
+ * The function is called when event received from firmware to update
+ * critical parameters to user space during probe or assoc or reassoc request
+ * frame receive and needed only on beacon offload case.
+ */
+void ieee80211_critical_update(struct ieee80211_vif *vif, unsigned int link_id,
+			       bool critical_flag, u8 bpcc);
 #endif /* MAC80211_H */
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index f03452dc716d..97ffd3028467 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -3606,6 +3606,7 @@ void ieee80211_csa_finish(struct ieee80211_vif *vif, unsigned int link_id)
 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_link_data *link_data;
+	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
 
 	if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS))
 		return;
@@ -3617,7 +3618,13 @@ void ieee80211_csa_finish(struct ieee80211_vif *vif, unsigned int link_id)
 		rcu_read_unlock();
 		return;
 	}
-
+	if (wiphy_ext_feature_isset(
+		    local->hw.wiphy,
+		    NL80211_EXT_FEATURE_CRITICAL_UPDATE_OFFLOAD) &&
+	    wdev->valid_links && wdev->links[link_id].ap.switch_count != 0) {
+		wdev->links[link_id].ap.switch_count = 0;
+		wdev->critical_update = true;
+	}
 	/* TODO: MBSSID with MLO changes */
 	if (vif->mbssid_tx_vif == vif) {
 		/* Trigger ieee80211_csa_finish() on the non-transmitting
@@ -3643,6 +3650,40 @@ void ieee80211_csa_finish(struct ieee80211_vif *vif, unsigned int link_id)
 }
 EXPORT_SYMBOL(ieee80211_csa_finish);
 
+/**
+ * ieee80211_critical_update - update critical params for each link
+ * @vif: the specified virtual interface
+ * @link_id: the link ID for MLO, otherwise 0
+ * @critical_flag: critical update information
+ * @bpcc: Bss parameter change count value
+ *
+ * The function is called when event received from firmware to update
+ * critical parameters to user space during probe or assoc or reassoc request
+ * frame receive and needed only on beacon offload case.
+ */
+void ieee80211_critical_update(struct ieee80211_vif *vif, unsigned int link_id,
+			       bool critical_flag, u8 bpcc)
+{
+	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
+	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+	struct ieee80211_local *local = sdata->local;
+
+	if (!wiphy_ext_feature_isset(
+		    local->hw.wiphy,
+		    NL80211_EXT_FEATURE_CRITICAL_UPDATE_OFFLOAD) ||
+	    !wdev->valid_links)
+		return;
+	if (WARN_ON(link_id > IEEE80211_MLD_MAX_NUM_LINKS))
+		return;
+	if (wdev->links[link_id].ap.critical_flag != critical_flag ||
+	    wdev->links[link_id].ap.bpcc != bpcc) {
+		wdev->critical_update = true;
+		wdev->links[link_id].ap.critical_flag = critical_flag;
+		wdev->links[link_id].ap.bpcc = bpcc;
+	}
+}
+EXPORT_SYMBOL(ieee80211_critical_update);
+
 void ieee80211_channel_switch_disconnect(struct ieee80211_vif *vif, bool block_tx)
 {
 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index c1f850138405..e60560b4e3e4 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3825,14 +3825,26 @@ static ieee80211_rx_result debug_noinline
 ieee80211_rx_h_userspace_mgmt(struct ieee80211_rx_data *rx)
 {
 	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
+	struct ieee80211_mgmt *mgmt = (void *)rx->skb->data;
+	__le16 stype;
+	struct wireless_dev *wdev = &rx->sdata->wdev;
+
 	struct cfg80211_rx_info info = {
 		.freq = ieee80211_rx_status_to_khz(status),
 		.buf = rx->skb->data,
 		.len = rx->skb->len,
 		.link_id = rx->link_id,
 		.have_link_id = rx->link_id >= 0,
+		.critical_update = false,
 	};
 
+	stype = mgmt->frame_control & cpu_to_le16(IEEE80211_FCTL_STYPE);
+	if (stype ==  cpu_to_le16(IEEE80211_STYPE_PROBE_REQ) ||
+	    stype ==  cpu_to_le16(IEEE80211_STYPE_ASSOC_REQ) ||
+	    stype ==  cpu_to_le16(IEEE80211_STYPE_REASSOC_REQ)) {
+		if (wdev->critical_update)
+			info.critical_update = true;
+	}
 	/* skip known-bad action frames and return them in the next handler */
 	if (status->rx_flags & IEEE80211_RX_MALFORMED_ACTION_FRM)
 		return RX_CONTINUE;
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 6bf223e6cd1a..3603cc582b0d 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -5041,6 +5041,8 @@ u8 ieee80211_beacon_update_cntdwn(struct ieee80211_vif *vif, unsigned int link_i
 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
 	struct ieee80211_link_data *link;
 	struct beacon_data *beacon = NULL;
+	struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
+	struct ieee80211_local *local = sdata->local;
 	u8 count = 0;
 
 	if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS))
@@ -5063,6 +5065,13 @@ u8 ieee80211_beacon_update_cntdwn(struct ieee80211_vif *vif, unsigned int link_i
 		goto unlock;
 
 	count = __ieee80211_beacon_update_cntdwn(beacon);
+	if (wiphy_ext_feature_isset(
+		    local->hw.wiphy,
+		    NL80211_EXT_FEATURE_CRITICAL_UPDATE_OFFLOAD) &&
+	    wdev->valid_links && wdev->links[link_id].ap.switch_count != count) {
+		wdev->links[link_id].ap.switch_count = count;
+		wdev->critical_update = true;
+	}
 
 unlock:
 	rcu_read_unlock();
