[WMI_KEY_USE_PAIRWISE]  = "PTK",
        [WMI_KEY_USE_RX_GROUP]  = "RX_GTK",
        [WMI_KEY_USE_TX_GROUP]  = "TX_GTK",
+       [WMI_KEY_USE_STORE_PTK] = "STORE_PTK",
+       [WMI_KEY_USE_APPLY_PTK] = "APPLY_PTK",
 };
 
 int wil_iftype_nl2wmi(enum nl80211_iftype type)
 /*
  * Find @idx-th active STA for specific MID for station dump.
  */
-static int wil_find_cid_by_idx(struct wil6210_priv *wil, u8 mid, int idx)
+int wil_find_cid_by_idx(struct wil6210_priv *wil, u8 mid, int idx)
 {
        int i;
 
                return;
 
        switch (key_usage) {
+       case WMI_KEY_USE_STORE_PTK:
        case WMI_KEY_USE_PAIRWISE:
                for (tid = 0; tid < WIL_STA_TID_NUM; tid++) {
                        cc = &cs->tid_crypto_rx[tid].key_id[key_index];
                return -EINVAL;
        }
 
+       spin_lock_bh(&wil->eap_lock);
+       if (pairwise && wdev->iftype == NL80211_IFTYPE_STATION &&
+           (vif->ptk_rekey_state == WIL_REKEY_M3_RECEIVED ||
+            vif->ptk_rekey_state == WIL_REKEY_WAIT_M4_SENT)) {
+               key_usage = WMI_KEY_USE_STORE_PTK;
+               vif->ptk_rekey_state = WIL_REKEY_WAIT_M4_SENT;
+               wil_dbg_misc(wil, "Store EAPOL key\n");
+       }
+       spin_unlock_bh(&wil->eap_lock);
+
        rc = wmi_add_cipher_key(vif, key_index, mac_addr, params->key_len,
                                params->key, key_usage);
        if (!rc && !IS_ERR(cs)) {
 
                }
                clear_bit(wil_vif_fwconnecting, vif->status);
                clear_bit(wil_vif_ft_roam, vif->status);
+               vif->ptk_rekey_state = WIL_REKEY_IDLE;
 
                break;
        case NL80211_IFTYPE_AP:
        INIT_LIST_HEAD(&wil->pending_wmi_ev);
        spin_lock_init(&wil->wmi_ev_lock);
        spin_lock_init(&wil->net_queue_lock);
+       spin_lock_init(&wil->eap_lock);
+
        init_waitqueue_head(&wil->wq);
        init_rwsem(&wil->mem_lock);
 
                        cancel_work_sync(&vif->disconnect_worker);
                        wil6210_disconnect(vif, NULL,
                                           WLAN_REASON_DEAUTH_LEAVING);
+                       vif->ptk_rekey_state = WIL_REKEY_IDLE;
                }
        }
        wil_bcast_fini_all(wil);
 
        cancel_work_sync(&vif->p2p.delayed_listen_work);
        wil_probe_client_flush(vif);
        cancel_work_sync(&vif->probe_client_worker);
+       cancel_work_sync(&vif->enable_tx_key_worker);
 }
 
 void wil_vif_free(struct wil6210_vif *vif)
        INIT_WORK(&vif->probe_client_worker, wil_probe_client_worker);
        INIT_WORK(&vif->disconnect_worker, wil_disconnect_worker);
        INIT_WORK(&vif->p2p.delayed_listen_work, wil_p2p_delayed_listen_work);
+       INIT_WORK(&vif->enable_tx_key_worker, wil_enable_tx_key_worker);
 
        INIT_LIST_HEAD(&vif->probe_client_pending);
 
        cancel_work_sync(&vif->disconnect_worker);
        wil_probe_client_flush(vif);
        cancel_work_sync(&vif->probe_client_worker);
+       cancel_work_sync(&vif->enable_tx_key_worker);
        /* for VIFs, ndev will be freed by destructor after RTNL is unlocked.
         * the main interface will be freed in wil_if_free, we need to keep it
         * a bit longer so logging macros will work.
 
        *security = wil_rxdesc_security(d);
 }
 
+/*
+ * Check if skb is ptk eapol key message
+ *
+ * returns a pointer to the start of the eapol key structure, NULL
+ * if frame is not PTK eapol key
+ */
+static struct wil_eapol_key *wil_is_ptk_eapol_key(struct wil6210_priv *wil,
+                                                 struct sk_buff *skb)
+{
+       u8 *buf;
+       const struct wil_1x_hdr *hdr;
+       struct wil_eapol_key *key;
+       u16 key_info;
+       int len = skb->len;
+
+       if (!skb_mac_header_was_set(skb)) {
+               wil_err(wil, "mac header was not set\n");
+               return NULL;
+       }
+
+       len -= skb_mac_offset(skb);
+
+       if (len < sizeof(struct ethhdr) + sizeof(struct wil_1x_hdr) +
+           sizeof(struct wil_eapol_key))
+               return NULL;
+
+       buf = skb_mac_header(skb) + sizeof(struct ethhdr);
+
+       hdr = (const struct wil_1x_hdr *)buf;
+       if (hdr->type != WIL_1X_TYPE_EAPOL_KEY)
+               return NULL;
+
+       key = (struct wil_eapol_key *)(buf + sizeof(struct wil_1x_hdr));
+       if (key->type != WIL_EAPOL_KEY_TYPE_WPA &&
+           key->type != WIL_EAPOL_KEY_TYPE_RSN)
+               return NULL;
+
+       key_info = be16_to_cpu(key->key_info);
+       if (!(key_info & WIL_KEY_INFO_KEY_TYPE)) /* check if pairwise */
+               return NULL;
+
+       return key;
+}
+
+static bool wil_skb_is_eap_3(struct wil6210_priv *wil, struct sk_buff *skb)
+{
+       struct wil_eapol_key *key;
+       u16 key_info;
+
+       key = wil_is_ptk_eapol_key(wil, skb);
+       if (!key)
+               return false;
+
+       key_info = be16_to_cpu(key->key_info);
+       if (key_info & (WIL_KEY_INFO_MIC |
+                       WIL_KEY_INFO_ENCR_KEY_DATA)) {
+               /* 3/4 of 4-Way Handshake */
+               wil_dbg_misc(wil, "EAPOL key message 3\n");
+               return true;
+       }
+       /* 1/4 of 4-Way Handshake */
+       wil_dbg_misc(wil, "EAPOL key message 1\n");
+
+       return false;
+}
+
+static bool wil_skb_is_eap_4(struct wil6210_priv *wil, struct sk_buff *skb)
+{
+       struct wil_eapol_key *key;
+       u32 *nonce, i;
+
+       key = wil_is_ptk_eapol_key(wil, skb);
+       if (!key)
+               return false;
+
+       nonce = (u32 *)key->key_nonce;
+       for (i = 0; i < WIL_EAP_NONCE_LEN / sizeof(u32); i++, nonce++) {
+               if (*nonce != 0) {
+                       /* message 2/4 */
+                       wil_dbg_misc(wil, "EAPOL key message 2\n");
+                       return false;
+               }
+       }
+       wil_dbg_misc(wil, "EAPOL key message 4\n");
+
+       return true;
+}
+
+void wil_enable_tx_key_worker(struct work_struct *work)
+{
+       struct wil6210_vif *vif = container_of(work,
+                       struct wil6210_vif, enable_tx_key_worker);
+       struct wil6210_priv *wil = vif_to_wil(vif);
+       int rc, cid;
+
+       rtnl_lock();
+       if (vif->ptk_rekey_state != WIL_REKEY_WAIT_M4_SENT) {
+               wil_dbg_misc(wil, "Invalid rekey state = %d\n",
+                            vif->ptk_rekey_state);
+               rtnl_unlock();
+               return;
+       }
+
+       cid =  wil_find_cid_by_idx(wil, vif->mid, 0);
+       if (!wil_cid_valid(wil, cid)) {
+               wil_err(wil, "Invalid cid = %d\n", cid);
+               rtnl_unlock();
+               return;
+       }
+
+       wil_dbg_misc(wil, "Apply PTK key after eapol was sent out\n");
+       rc = wmi_add_cipher_key(vif, 0, wil->sta[cid].addr, 0, NULL,
+                               WMI_KEY_USE_APPLY_PTK);
+
+       vif->ptk_rekey_state = WIL_REKEY_IDLE;
+       rtnl_unlock();
+
+       if (rc)
+               wil_err(wil, "Apply PTK key failed %d\n", rc);
+}
+
+void wil_tx_complete_handle_eapol(struct wil6210_vif *vif, struct sk_buff *skb)
+{
+       struct wil6210_priv *wil = vif_to_wil(vif);
+       struct wireless_dev *wdev = vif_to_wdev(vif);
+       bool q = false;
+
+       if (wdev->iftype != NL80211_IFTYPE_STATION ||
+           !test_bit(WMI_FW_CAPABILITY_SPLIT_REKEY, wil->fw_capabilities))
+               return;
+
+       /* check if skb is an EAP message 4/4 */
+       if (!wil_skb_is_eap_4(wil, skb))
+               return;
+
+       spin_lock_bh(&wil->eap_lock);
+       switch (vif->ptk_rekey_state) {
+       case WIL_REKEY_IDLE:
+               /* ignore idle state, can happen due to M4 retransmission */
+               break;
+       case WIL_REKEY_M3_RECEIVED:
+               vif->ptk_rekey_state = WIL_REKEY_IDLE;
+               break;
+       case WIL_REKEY_WAIT_M4_SENT:
+               q = true;
+               break;
+       default:
+               wil_err(wil, "Unknown rekey state = %d",
+                       vif->ptk_rekey_state);
+       }
+       spin_unlock_bh(&wil->eap_lock);
+
+       if (q) {
+               q = queue_work(wil->wmi_wq, &vif->enable_tx_key_worker);
+               wil_dbg_misc(wil, "queue_work of enable_tx_key_worker -> %d\n",
+                            q);
+       }
+}
+
+static void wil_rx_handle_eapol(struct wil6210_vif *vif, struct sk_buff *skb)
+{
+       struct wil6210_priv *wil = vif_to_wil(vif);
+       struct wireless_dev *wdev = vif_to_wdev(vif);
+
+       if (wdev->iftype != NL80211_IFTYPE_STATION ||
+           !test_bit(WMI_FW_CAPABILITY_SPLIT_REKEY, wil->fw_capabilities))
+               return;
+
+       /* check if skb is a EAP message 3/4 */
+       if (!wil_skb_is_eap_3(wil, skb))
+               return;
+
+       if (vif->ptk_rekey_state == WIL_REKEY_IDLE)
+               vif->ptk_rekey_state = WIL_REKEY_M3_RECEIVED;
+}
+
 /*
  * Pass Rx packet to the netif. Update statistics.
  * Called in softirq context (NAPI poll).
        if (skb) { /* deliver to local stack */
                skb->protocol = eth_type_trans(skb, ndev);
                skb->dev = ndev;
+
+               if (skb->protocol == cpu_to_be16(ETH_P_PAE))
+                       wil_rx_handle_eapol(vif, skb);
+
                if (gro)
                        rc = napi_gro_receive(&wil->napi_rx, skb);
                else
                                        if (stats)
                                                stats->tx_errors++;
                                }
+
+                               if (skb->protocol == cpu_to_be16(ETH_P_PAE))
+                                       wil_tx_complete_handle_eapol(vif, skb);
+
                                wil_consume_skb(skb, d->dma.error == 0);
                        }
                        memset(ctx, 0, sizeof(*ctx));
 
 #define RX_DMA_STATUS_PHY_INFO BIT(6)
 #define RX_DMA_STATUS_FFM      BIT(7) /* EtherType Flex Filter Match */
 
+/* IEEE 802.11, 8.5.2 EAPOL-Key frames */
+#define WIL_KEY_INFO_KEY_TYPE BIT(3) /* val of 1 = Pairwise, 0 = Group key */
+
+#define WIL_KEY_INFO_MIC BIT(8)
+#define WIL_KEY_INFO_ENCR_KEY_DATA BIT(12) /* for rsn only */
+
+#define WIL_EAP_NONCE_LEN 32
+#define WIL_EAP_KEY_RSC_LEN 8
+#define WIL_EAP_REPLAY_COUNTER_LEN 8
+#define WIL_EAP_KEY_IV_LEN 16
+#define WIL_EAP_KEY_ID_LEN 8
+
+enum {
+       WIL_1X_TYPE_EAP_PACKET = 0,
+       WIL_1X_TYPE_EAPOL_START = 1,
+       WIL_1X_TYPE_EAPOL_LOGOFF = 2,
+       WIL_1X_TYPE_EAPOL_KEY = 3,
+};
+
+#define WIL_EAPOL_KEY_TYPE_RSN 2
+#define WIL_EAPOL_KEY_TYPE_WPA 254
+
+struct wil_1x_hdr {
+       u8 version;
+       u8 type;
+       __be16 length;
+       /* followed by data */
+} __packed;
+
+struct wil_eapol_key {
+       u8 type;
+       __be16 key_info;
+       __be16 key_length;
+       u8 replay_counter[WIL_EAP_REPLAY_COUNTER_LEN];
+       u8 key_nonce[WIL_EAP_NONCE_LEN];
+       u8 key_iv[WIL_EAP_KEY_IV_LEN];
+       u8 key_rsc[WIL_EAP_KEY_RSC_LEN];
+       u8 key_id[WIL_EAP_KEY_ID_LEN];
+} __packed;
+
 struct vring_rx_dma {
        u32 d0;
        struct wil_ring_dma_addr addr;
 
                                        if (stats)
                                                stats->tx_errors++;
                                }
+
+                               if (skb->protocol == cpu_to_be16(ETH_P_PAE))
+                                       wil_tx_complete_handle_eapol(vif, skb);
+
                                wil_consume_skb(skb, msg.status == 0);
                        }
                        memset(ctx, 0, sizeof(*ctx));
 
        wil_sta_connected = 2,
 };
 
+enum wil_rekey_state {
+       WIL_REKEY_IDLE = 0,
+       WIL_REKEY_M3_RECEIVED = 1,
+       WIL_REKEY_WAIT_M4_SENT = 2,
+};
+
 /**
  * struct wil_sta_info - data for peer
  *
        int net_queue_stopped; /* netif_tx_stop_all_queues invoked */
        bool fw_stats_ready; /* per-cid statistics are ready inside sta_info */
        u64 fw_stats_tsf; /* measurement timestamp */
+
+       /* PTK rekey race prevention, this is relevant to station mode only */
+       enum wil_rekey_state ptk_rekey_state;
+       struct work_struct enable_tx_key_worker;
 };
 
 /**
         */
        spinlock_t wmi_ev_lock;
        spinlock_t net_queue_lock; /* guarding stop/wake netif queue */
+       spinlock_t eap_lock; /* guarding access to eap rekey fields */
        struct napi_struct napi_rx;
        struct napi_struct napi_tx;
        struct net_device napi_ndev; /* dummy net_device serving all VIFs */
 void wil_refresh_fw_capabilities(struct wil6210_priv *wil);
 void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r);
 int wil_find_cid(struct wil6210_priv *wil, u8 mid, const u8 *mac);
+int wil_find_cid_by_idx(struct wil6210_priv *wil, u8 mid, int idx);
 void wil_set_ethtoolops(struct net_device *ndev);
 
 struct fw_map *wil_find_fw_mapping(const char *section);
 void wil_probe_client_flush(struct wil6210_vif *vif);
 void wil_probe_client_worker(struct work_struct *work);
 void wil_disconnect_worker(struct work_struct *work);
+void wil_enable_tx_key_worker(struct work_struct *work);
 
 void wil_init_txrx_ops(struct wil6210_priv *wil);
 
                              struct wil_ring *ring, bool check_stop);
 netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev);
 int wil_tx_complete(struct wil6210_vif *vif, int ringid);
+void wil_tx_complete_handle_eapol(struct wil6210_vif *vif,
+                                 struct sk_buff *skb);
 void wil6210_unmask_irq_tx(struct wil6210_priv *wil);
 void wil6210_unmask_irq_tx_edma(struct wil6210_priv *wil);
 
 
                .key_len = key_len,
        };
 
-       if (!key || (key_len > sizeof(cmd.key)))
+       if (key_len > sizeof(cmd.key))
                return -EINVAL;
 
-       memcpy(cmd.key, key, key_len);
+       /* key len = 0 is allowed only for usage of WMI_KEY_USE_APPLY */
+       if ((key_len == 0 || !key) &&
+           key_usage != WMI_KEY_USE_APPLY_PTK)
+               return -EINVAL;
+
+       if (key)
+               memcpy(cmd.key, key, key_len);
+
        if (mac_addr)
                memcpy(cmd.mac, mac_addr, WMI_MAC_LEN);
 
 
        WMI_FW_CAPABILITY_CHANNEL_4                     = 26,
        WMI_FW_CAPABILITY_IPA                           = 27,
        WMI_FW_CAPABILITY_TEMPERATURE_ALL_RF            = 30,
+       WMI_FW_CAPABILITY_SPLIT_REKEY                   = 31,
        WMI_FW_CAPABILITY_MAX,
 };
 
        WMI_KEY_USE_PAIRWISE    = 0x00,
        WMI_KEY_USE_RX_GROUP    = 0x01,
        WMI_KEY_USE_TX_GROUP    = 0x02,
+       WMI_KEY_USE_STORE_PTK   = 0x03,
+       WMI_KEY_USE_APPLY_PTK   = 0x04,
 };
 
 struct wmi_add_cipher_key_cmd {