IEEE80211_RECONFIG_TYPE_SUSPEND,
 };
 
+/**
+ * struct ieee80211_prep_tx_info - prepare TX information
+ * @duration: if non-zero, hint about the required duration,
+ *     only used with the mgd_prepare_tx() method.
+ * @subtype: frame subtype (auth, (re)assoc, deauth, disassoc)
+ * @success: whether the frame exchange was successful, only
+ *     used with the mgd_complete_tx() method, and then only
+ *     valid for auth and (re)assoc.
+ */
+struct ieee80211_prep_tx_info {
+       u16 duration;
+       u16 subtype;
+       u8 success:1;
+};
+
 /**
  * struct ieee80211_ops - callbacks from mac80211 to the driver
  *
  *     frame in case that no beacon was heard from the AP/P2P GO.
  *     The callback will be called before each transmission and upon return
  *     mac80211 will transmit the frame right away.
- *      If duration is greater than zero, mac80211 hints to the driver the
- *      duration for which the operation is requested.
+ *     Additional information is passed in the &struct ieee80211_prep_tx_info
+ *     data. If duration there is greater than zero, mac80211 hints to the
+ *     driver the duration for which the operation is requested.
  *     The callback is optional and can (should!) sleep.
+ * @mgd_complete_tx: Notify the driver that the response frame for a previously
+ *     transmitted frame announced with @mgd_prepare_tx was received, the data
+ *     is filled similarly to @mgd_prepare_tx though the duration is not used.
  *
  * @mgd_protect_tdls_discover: Protect a TDLS discovery session. After sending
  *     a TDLS discovery-request, we expect a reply to arrive on the AP's
 
        void    (*mgd_prepare_tx)(struct ieee80211_hw *hw,
                                  struct ieee80211_vif *vif,
-                                 u16 duration);
+                                 struct ieee80211_prep_tx_info *info);
+       void    (*mgd_complete_tx)(struct ieee80211_hw *hw,
+                                  struct ieee80211_vif *vif,
+                                  struct ieee80211_prep_tx_info *info);
 
        void    (*mgd_protect_tdls_discover)(struct ieee80211_hw *hw,
                                             struct ieee80211_vif *vif);
 
        struct element *ext_capa = NULL;
        enum nl80211_iftype iftype = ieee80211_vif_type_p2p(&sdata->vif);
        const struct ieee80211_sband_iftype_data *iftd;
+       struct ieee80211_prep_tx_info info = {};
 
        /* we know it's writable, cast away the const */
        if (assoc_data->ie_len)
                mgmt->u.reassoc_req.listen_interval = listen_int;
                memcpy(mgmt->u.reassoc_req.current_ap, assoc_data->prev_bssid,
                       ETH_ALEN);
+               info.subtype = IEEE80211_STYPE_REASSOC_REQ;
        } else {
                skb_put(skb, 4);
                mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
                                                  IEEE80211_STYPE_ASSOC_REQ);
                mgmt->u.assoc_req.capab_info = cpu_to_le16(capab);
                mgmt->u.assoc_req.listen_interval = listen_int;
+               info.subtype = IEEE80211_STYPE_ASSOC_REQ;
        }
 
        /* SSID */
        ifmgd->assoc_req_ies = kmemdup(ie_start, pos - ie_start, GFP_ATOMIC);
        ifmgd->assoc_req_ies_len = pos - ie_start;
 
-       drv_mgd_prepare_tx(local, sdata, 0);
+       drv_mgd_prepare_tx(local, sdata, &info);
 
        IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
        if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_local *local = sdata->local;
        u32 changed = 0;
+       struct ieee80211_prep_tx_info info = {
+               .subtype = stype,
+       };
 
        sdata_assert_lock(sdata);
 
                 * driver requested so.
                 */
                if (ieee80211_hw_check(&local->hw, DEAUTH_NEED_MGD_TX_PREP) &&
-                   !ifmgd->have_beacon)
-                       drv_mgd_prepare_tx(sdata->local, sdata, 0);
+                   !ifmgd->have_beacon) {
+                       drv_mgd_prepare_tx(sdata->local, sdata, &info);
+               }
 
                ieee80211_send_deauth_disassoc(sdata, ifmgd->bssid,
                                               ifmgd->bssid, stype, reason,
        if (tx)
                ieee80211_flush_queues(local, sdata, false);
 
+       drv_mgd_complete_tx(sdata->local, sdata, &info);
+
        /* clear bssid only after building the needed mgmt frames */
        eth_zero_addr(ifmgd->bssid);
 
        u8 *pos;
        struct ieee802_11_elems elems;
        u32 tx_flags = 0;
+       struct ieee80211_prep_tx_info info = {
+               .subtype = IEEE80211_STYPE_AUTH,
+       };
 
        pos = mgmt->u.auth.variable;
        ieee802_11_parse_elems(pos, len - (pos - (u8 *)mgmt), false, &elems,
        if (!elems.challenge)
                return;
        auth_data->expected_transaction = 4;
-       drv_mgd_prepare_tx(sdata->local, sdata, 0);
+       drv_mgd_prepare_tx(sdata->local, sdata, &info);
        if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
                tx_flags = IEEE80211_TX_CTL_REQ_TX_STATUS |
                           IEEE80211_TX_INTFL_MLME_CONN_TX;
                .type = MLME_EVENT,
                .u.mlme.data = AUTH_EVENT,
        };
+       struct ieee80211_prep_tx_info info = {
+               .subtype = IEEE80211_STYPE_AUTH,
+       };
 
        sdata_assert_lock(sdata);
 
                           mgmt->sa, auth_alg, ifmgd->auth_data->algorithm,
                           auth_transaction,
                           ifmgd->auth_data->expected_transaction);
-               return;
+               goto notify_driver;
        }
 
        if (status_code != WLAN_STATUS_SUCCESS) {
                     (auth_transaction == 1 &&
                      (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
                       status_code == WLAN_STATUS_SAE_PK))))
-                       return;
+                       goto notify_driver;
 
                sdata_info(sdata, "%pM denied authentication (status %d)\n",
                           mgmt->sa, status_code);
                event.u.mlme.status = MLME_DENIED;
                event.u.mlme.reason = status_code;
                drv_event_callback(sdata->local, sdata, &event);
-               return;
+               goto notify_driver;
        }
 
        switch (ifmgd->auth_data->algorithm) {
        default:
                WARN_ONCE(1, "invalid auth alg %d",
                          ifmgd->auth_data->algorithm);
-               return;
+               goto notify_driver;
        }
 
        event.u.mlme.status = MLME_SUCCESS;
+       info.success = 1;
        drv_event_callback(sdata->local, sdata, &event);
        if (ifmgd->auth_data->algorithm != WLAN_AUTH_SAE ||
            (auth_transaction == 2 &&
        }
 
        cfg80211_rx_mlme_mgmt(sdata->dev, (u8 *)mgmt, len);
+notify_driver:
+       drv_mgd_complete_tx(sdata->local, sdata, &info);
 }
 
 #define case_WLAN(type) \
                .type = MLME_EVENT,
                .u.mlme.data = ASSOC_EVENT,
        };
+       struct ieee80211_prep_tx_info info = {};
 
        sdata_assert_lock(sdata);
 
                aid = 0; /* TODO */
        }
 
+       /*
+        * Note: this may not be perfect, AP might misbehave - if
+        * anyone needs to rely on perfect complete notification
+        * with the exact right subtype, then we need to track what
+        * we actually transmitted.
+        */
+       info.subtype = reassoc ? IEEE80211_STYPE_REASSOC_REQ :
+                                IEEE80211_STYPE_ASSOC_REQ;
+
        sdata_info(sdata,
                   "RX %sssocResp from %pM (capab=0x%x status=%d aid=%d)\n",
                   reassoc ? "Rea" : "A", mgmt->sa,
                assoc_data->timeout_started = true;
                if (ms > IEEE80211_ASSOC_TIMEOUT)
                        run_again(sdata, assoc_data->timeout);
-               return;
+               goto notify_driver;
        }
 
        if (status_code != WLAN_STATUS_SUCCESS) {
                        /* oops -- internal error -- send timeout for now */
                        ieee80211_destroy_assoc_data(sdata, false, false);
                        cfg80211_assoc_timeout(sdata->dev, cbss);
-                       return;
+                       goto notify_driver;
                }
                event.u.mlme.status = MLME_SUCCESS;
                drv_event_callback(sdata->local, sdata, &event);
                for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
                        if (sdata->tx_conf[ac].uapsd)
                                uapsd_queues |= ieee80211_ac_to_qos_mask[ac];
+
+               info.success = 1;
        }
 
        cfg80211_rx_assoc_resp(sdata->dev, cbss, (u8 *)mgmt, len, uapsd_queues,
                               ifmgd->assoc_req_ies, ifmgd->assoc_req_ies_len);
+notify_driver:
+       drv_mgd_complete_tx(sdata->local, sdata, &info);
 }
 
 static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
        u32 tx_flags = 0;
        u16 trans = 1;
        u16 status = 0;
-       u16 prepare_tx_duration = 0;
+       struct ieee80211_prep_tx_info info = {
+               .subtype = IEEE80211_STYPE_AUTH,
+       };
 
        sdata_assert_lock(sdata);
 
        }
 
        if (auth_data->algorithm == WLAN_AUTH_SAE)
-               prepare_tx_duration =
-                       jiffies_to_msecs(IEEE80211_AUTH_TIMEOUT_SAE);
+               info.duration = jiffies_to_msecs(IEEE80211_AUTH_TIMEOUT_SAE);
 
-       drv_mgd_prepare_tx(local, sdata, prepare_tx_duration);
+       drv_mgd_prepare_tx(local, sdata, &info);
 
        sdata_info(sdata, "send auth to %pM (try %d/%d)\n",
                   auth_data->bss->bssid, auth_data->tries,
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN];
        bool tx = !req->local_state_change;
+       struct ieee80211_prep_tx_info info = {
+               .subtype = IEEE80211_STYPE_DEAUTH,
+       };
 
        if (ifmgd->auth_data &&
            ether_addr_equal(ifmgd->auth_data->bss->bssid, req->bssid)) {
                           req->bssid, req->reason_code,
                           ieee80211_get_reason_code_string(req->reason_code));
 
-               drv_mgd_prepare_tx(sdata->local, sdata, 0);
+               drv_mgd_prepare_tx(sdata->local, sdata, &info);
                ieee80211_send_deauth_disassoc(sdata, req->bssid, req->bssid,
                                               IEEE80211_STYPE_DEAUTH,
                                               req->reason_code, tx,
                ieee80211_report_disconnect(sdata, frame_buf,
                                            sizeof(frame_buf), true,
                                            req->reason_code, false);
-
+               drv_mgd_complete_tx(sdata->local, sdata, &info);
                return 0;
        }
 
                           req->bssid, req->reason_code,
                           ieee80211_get_reason_code_string(req->reason_code));
 
-               drv_mgd_prepare_tx(sdata->local, sdata, 0);
+               drv_mgd_prepare_tx(sdata->local, sdata, &info);
                ieee80211_send_deauth_disassoc(sdata, req->bssid, req->bssid,
                                               IEEE80211_STYPE_DEAUTH,
                                               req->reason_code, tx,
                ieee80211_report_disconnect(sdata, frame_buf,
                                            sizeof(frame_buf), true,
                                            req->reason_code, false);
+               drv_mgd_complete_tx(sdata->local, sdata, &info);
                return 0;
        }
 
 
 /*
 * Portions of this file
 * Copyright(c) 2016-2017 Intel Deutschland GmbH
-* Copyright (C) 2018 - 2020 Intel Corporation
+* Copyright (C) 2018 - 2021 Intel Corporation
 */
 
 #if !defined(__MAC80211_DRIVER_TRACE) || defined(TRACE_HEADER_MULTI_READ)
        TP_ARGS(local, sta, tids, num_frames, reason, more_data)
 );
 
-TRACE_EVENT(drv_mgd_prepare_tx,
+DECLARE_EVENT_CLASS(mgd_prepare_complete_tx_evt,
        TP_PROTO(struct ieee80211_local *local,
                 struct ieee80211_sub_if_data *sdata,
-                u16 duration),
+                u16 duration, u16 subtype, bool success),
 
-       TP_ARGS(local, sdata, duration),
+       TP_ARGS(local, sdata, duration, subtype, success),
 
        TP_STRUCT__entry(
                LOCAL_ENTRY
                VIF_ENTRY
                __field(u32, duration)
+               __field(u16, subtype)
+               __field(u8, success)
        ),
 
        TP_fast_assign(
                LOCAL_ASSIGN;
                VIF_ASSIGN;
                __entry->duration = duration;
+               __entry->subtype = subtype;
+               __entry->success = success;
        ),
 
        TP_printk(
-               LOCAL_PR_FMT VIF_PR_FMT " duration: %u",
-               LOCAL_PR_ARG, VIF_PR_ARG, __entry->duration
+               LOCAL_PR_FMT VIF_PR_FMT " duration: %u, subtype:0x%x, success:%d",
+               LOCAL_PR_ARG, VIF_PR_ARG, __entry->duration,
+               __entry->subtype, __entry->success
        )
 );
 
+DEFINE_EVENT(mgd_prepare_complete_tx_evt, drv_mgd_prepare_tx,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                u16 duration, u16 subtype, bool success),
+
+       TP_ARGS(local, sdata, duration, subtype, success)
+);
+
+DEFINE_EVENT(mgd_prepare_complete_tx_evt, drv_mgd_complete_tx,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                u16 duration, u16 subtype, bool success),
+
+       TP_ARGS(local, sdata, duration, subtype, success)
+);
+
 DEFINE_EVENT(local_sdata_evt, drv_mgd_protect_tdls_discover,
        TP_PROTO(struct ieee80211_local *local,
                 struct ieee80211_sub_if_data *sdata),