[iwlwifi] Update to latest driver.

Updates the driver to the latest one from ToT. Also contains
the necessary changed to make it compile against the latest
build of partner sdk.

Change-Id: I7ae01c41c3f35563984e60dcad738f427c077e95
Reviewed-on: https://fuchsia-review.googlesource.com/c/drivers/wlan/intel/iwlwifi/+/640827
Fuchsia-Auto-Submit: Sakthi Vignesh Radhakrishnan <rsakthi@google.com>
Reviewed-by: Sean Cuff <seancuff@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/third_party/iwlwifi/BUILD.bazel b/third_party/iwlwifi/BUILD.bazel
index 3d258ba..94b2781 100644
--- a/third_party/iwlwifi/BUILD.bazel
+++ b/third_party/iwlwifi/BUILD.bazel
@@ -1,3 +1,7 @@
+# Copyright 2021 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
 package(default_visibility = ["//visibility:public"])
 
 cc_library(
@@ -8,41 +12,33 @@
         "iwl-io.c",
         "iwl-nvm-parse.c",
         "iwl-phy-db.c",
-       "iwl-trans.c",
+        "iwl-trans.c",
         ],
     hdrs = [
-       "iwl-agn-hw.h",
-       "iwl-config.h",
+        "iwl-agn-hw.h",
+        "iwl-config.h",
         "iwl-constants.h",
-       "iwl-csr.h",
+        "iwl-csr.h",
         "iwl-dbg-tlv.h",
         "iwl-debug.h",
         "iwl-drv.h",
         "iwl-eeprom-parse.h",
         "iwl-eeprom-read.h",
         "iwl-fh.h",
-       "iwl-io.h",
-       "iwl-modparams.h",
-       "iwl-nvm-parse.h",
-       "iwl-op-mode.h",
-       "iwl-phy-db.h",
-       "iwl-prph.h",
-       "iwl-scd.h",
-       "iwl-trans.h",
-       "iwl-vendor-cmd.h",
-       "iwl-context-info-gen3.h",
-       "iwl-context-info.h",
+        "iwl-io.h",
+        "iwl-modparams.h",
+        "iwl-nvm-parse.h",
+        "iwl-op-mode.h",
+        "iwl-phy-db.h",
+        "iwl-prph.h",
+        "iwl-scd.h",
+        "iwl-trans.h",
+        "iwl-vendor-cmd.h",
+        "iwl-context-info-gen3.h",
+        "iwl-context-info.h",
         ],
     deps = [
         "//third_party/iwlwifi/fw:api",
-#        "//third_party/iwlwifi/platform:platform",
-#        "@fuchsia_sdk//pkg/ddk",
-#        "//zircon/system/public",
-#        "//zircon/system/ulib/sync",
-#        "//zircon/system/ulib/zircon-internal",
-
-#        "//sdk/banjo/ddk.hw.wlan.wlaninfo:ddk.hw.wlan.wlaninfo_banjo_c",
-#        "//zircon/system/public",
+        "//third_party/iwlwifi/platform:platform",
     ],
 )
-
diff --git a/third_party/iwlwifi/BUILD.gn b/third_party/iwlwifi/BUILD.gn
index 9ec51cf..588d580 100644
--- a/third_party/iwlwifi/BUILD.gn
+++ b/third_party/iwlwifi/BUILD.gn
@@ -5,7 +5,7 @@
 import("//build/components.gni")
 import("//build/drivers.gni")
 
-visibility = [ "//src/iwlwifi/*" ]
+visibility = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/*" ]
 
 # Common configuration for builds on Fuchsia.
 config("fuchsia_config") {
@@ -47,15 +47,14 @@
     "iwl-vendor-cmd.h",
   ]
   deps = [
-    "//src/iwlwifi/fw:api",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw:api",
     "//src/lib/ddk",
     "//zircon/system/public",
     "//zircon/system/ulib/sync",
-    "//zircon/system/ulib/zircon-internal",
   ]
   public_deps = [
-    "//sdk/banjo/ddk.hw.wlan.wlaninfo:ddk.hw.wlan.wlaninfo_banjo_c",
-    "//src/iwlwifi/platform",
+    "//sdk/banjo/fuchsia.hardware.wlan.phyinfo:fuchsia.hardware.wlan.phyinfo_banjo_c",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//zircon/system/public",
   ]
 
@@ -65,10 +64,11 @@
 
 fuchsia_driver("iwlwifi_driver-driver") {
   output_name = "iwlwifi"
-  deps = [ "//src/iwlwifi/platform:fuchsia_device" ]
+  deps = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:fuchsia_device" ]
 }
 
 fuchsia_driver_component("iwlwifi_driver") {
+  info = "iwlwifi_driver-info.json"
   component_name = "iwlwifi"
   deps = [ ":iwlwifi_driver-driver" ]
   visibility = []
@@ -102,8 +102,8 @@
 group("tests") {
   testonly = true
   deps = [
-    "//src/iwlwifi/platform:fuchsia_bind_test",
-    "//src/iwlwifi/test:iwlwifi_test",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:fuchsia_bind_test",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/test:iwlwifi_test",
   ]
   visibility = []
   visibility = [ "*" ]
diff --git a/third_party/iwlwifi/README.md b/third_party/iwlwifi/README.md
index f1d3ea2..5b7d0eb 100644
--- a/third_party/iwlwifi/README.md
+++ b/third_party/iwlwifi/README.md
@@ -45,7 +45,7 @@
 ```
                  MLME
                   |
-           --------------- wlanphy_impl_protocol_ops_t / wlanmac_protocol_ops_t
+           --------------- wlanphy_impl_protocol_ops_t / wlan_softmac_protocol_ops_t
                   |
      ^            |
      |     +-------------+         +--------------+
diff --git a/third_party/iwlwifi/cfg/7000.c b/third_party/iwlwifi/cfg/7000.c
index 36da06f..b054c7f 100644
--- a/third_party/iwlwifi/cfg/7000.c
+++ b/third_party/iwlwifi/cfg/7000.c
@@ -115,7 +115,7 @@
 
 static const struct iwl_ht_params iwl7000_ht_params = {
     .stbc = true,
-    .ht40_bands = BIT(WLAN_INFO_BAND_2GHZ) | BIT(WLAN_INFO_BAND_5GHZ),
+    .ht40_bands = BIT(WLAN_INFO_BAND_TWO_GHZ) | BIT(WLAN_INFO_BAND_FIVE_GHZ),
 };
 
 #define IWL_DEVICE_7000_COMMON                                                             \
@@ -226,7 +226,7 @@
 static const struct iwl_ht_params iwl7265_ht_params = {
     .stbc = true,
     .ldpc = true,
-    .ht40_bands = BIT(WLAN_INFO_BAND_2GHZ) | BIT(WLAN_INFO_BAND_5GHZ),
+    .ht40_bands = BIT(WLAN_INFO_BAND_TWO_GHZ) | BIT(WLAN_INFO_BAND_FIVE_GHZ),
 };
 
 const struct iwl_cfg iwl3165_2ac_cfg = {
diff --git a/third_party/iwlwifi/cfg/8000.c b/third_party/iwlwifi/cfg/8000.c
index ca344d8..110a0e1 100644
--- a/third_party/iwlwifi/cfg/8000.c
+++ b/third_party/iwlwifi/cfg/8000.c
@@ -77,7 +77,7 @@
 static const struct iwl_ht_params iwl8000_ht_params = {
     .stbc = true,
     .ldpc = true,
-    .ht40_bands = BIT(WLAN_INFO_BAND_2GHZ) | BIT(WLAN_INFO_BAND_5GHZ),
+    .ht40_bands = BIT(WLAN_INFO_BAND_TWO_GHZ) | BIT(WLAN_INFO_BAND_FIVE_GHZ),
 };
 
 static const struct iwl_tt_params iwl8000_tt_params = {
diff --git a/third_party/iwlwifi/cfg/9000.c b/third_party/iwlwifi/cfg/9000.c
index ae96844..85fbf5d 100644
--- a/third_party/iwlwifi/cfg/9000.c
+++ b/third_party/iwlwifi/cfg/9000.c
@@ -72,7 +72,7 @@
 static const struct iwl_ht_params iwl9000_ht_params = {
     .stbc = true,
     .ldpc = true,
-    .ht40_bands = BIT(WLAN_INFO_BAND_2GHZ) | BIT(WLAN_INFO_BAND_5GHZ),
+    .ht40_bands = BIT(WLAN_INFO_BAND_TWO_GHZ) | BIT(WLAN_INFO_BAND_FIVE_GHZ),
 };
 
 static const struct iwl_tt_params iwl9000_tt_params = {
diff --git a/third_party/iwlwifi/cfg/BUILD.gn b/third_party/iwlwifi/cfg/BUILD.gn
index 10ad30a..6d98072 100644
--- a/third_party/iwlwifi/cfg/BUILD.gn
+++ b/third_party/iwlwifi/cfg/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-visibility = [ "//src/iwlwifi/*" ]
+visibility = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/*" ]
 
 source_set("cfg") {
   sources = [
@@ -11,7 +11,7 @@
     "9000.c",
   ]
   deps = [
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/fw:api",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw:api",
   ]
 }
diff --git a/third_party/iwlwifi/fw/BUILD.bazel b/third_party/iwlwifi/fw/BUILD.bazel
index 9b8975c..7a841ef 100644
--- a/third_party/iwlwifi/fw/BUILD.bazel
+++ b/third_party/iwlwifi/fw/BUILD.bazel
@@ -75,7 +75,5 @@
         ":api",
         "//third_party/iwlwifi:core",
         "//third_party/iwlwifi/platform:platform",
-#    "//zircon/system/public",
-#    "//zircon/system/ulib/sync",
         ],
 )
diff --git a/third_party/iwlwifi/fw/BUILD.gn b/third_party/iwlwifi/fw/BUILD.gn
index f7035c9..7ccedcb 100644
--- a/third_party/iwlwifi/fw/BUILD.gn
+++ b/third_party/iwlwifi/fw/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-visibility = [ "//src/iwlwifi/*" ]
+visibility = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/*" ]
 
 # These are the headers for the firmware API.  To avoid circular dependencies, these do not depend
 # on anything in iwlwifi other than platform/.
@@ -50,7 +50,7 @@
   ]
   public_deps = [
     "//sdk/fidl/fuchsia.wlan.ieee80211:fuchsia.wlan.ieee80211_c",
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//src/lib/ddk",
   ]
 }
@@ -70,8 +70,8 @@
   ]
   public_deps = [
     ":api",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//zircon/system/public",
     "//zircon/system/ulib/sync",
   ]
diff --git a/third_party/iwlwifi/fw/api/fmac.h b/third_party/iwlwifi/fw/api/fmac.h
index 7d84de6..5f97d66 100644
--- a/third_party/iwlwifi/fw/api/fmac.h
+++ b/third_party/iwlwifi/fw/api/fmac.h
@@ -35,7 +35,7 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_FW_API_FMAC_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_FW_API_FMAC_H_
 
-#include <fuchsia/wlan/ieee80211/c/fidl.h>
+#include <fuchsia/wlan/ieee80211/c/banjo.h>
 
 #define FMAC_GROUP 0x10
 
diff --git a/third_party/iwlwifi/fw/dbg.c b/third_party/iwlwifi/fw/dbg.c
index ab7359e..167258d 100644
--- a/third_party/iwlwifi/fw/dbg.c
+++ b/third_party/iwlwifi/fw/dbg.c
@@ -1417,7 +1417,7 @@
   }
 
   iwl_write_prph(trans, DBGC_IN_SAMPLE, 0);
-  zx_nanosleep(ZX_USEC(100));
+  zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
   iwl_write_prph(trans, DBGC_OUT_CTRL, 0);
 #ifdef CPTCFG_IWLWIFI_DEBUGFS
   trans->dbg_rec_on = false;
@@ -1445,7 +1445,7 @@
     iwl_set_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x1);
   } else {
     iwl_write_prph(trans, DBGC_IN_SAMPLE, params->in_sample);
-    zx_nanosleep(ZX_USEC(100));
+    zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
     iwl_write_prph(trans, DBGC_OUT_CTRL, params->out_ctrl);
   }
 }
@@ -1494,7 +1494,7 @@
   /* start recording again if the firmware is not crashed */
   if (!test_bit(STATUS_FW_ERROR, &fwrt->trans->status) && fwrt->fw->dbg.dest_tlv) {
     /* wait before we collect the data till the DBGC stop */
-    zx_nanosleep(ZX_USEC(500));
+    zx_nanosleep(zx_deadline_after(ZX_USEC(500)));
     iwl_fw_dbg_restart_recording(fwrt, &params);
   }
 }
diff --git a/third_party/iwlwifi/iwl-dbg-cfg.c b/third_party/iwlwifi/iwl-dbg-cfg.c
index 28a45e0..94591d0 100644
--- a/third_party/iwlwifi/iwl-dbg-cfg.c
+++ b/third_party/iwlwifi/iwl-dbg-cfg.c
@@ -32,9 +32,6 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *****************************************************************************/
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
 #include "iwl-dbg-cfg.h"
 
 #include <linux/export.h>
diff --git a/third_party/iwlwifi/iwl-dbg-cfg.h b/third_party/iwlwifi/iwl-dbg-cfg.h
index 1159aed..9c45136 100644
--- a/third_party/iwlwifi/iwl-dbg-cfg.h
+++ b/third_party/iwlwifi/iwl-dbg-cfg.h
@@ -35,9 +35,6 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_IWL_DBG_CFG_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_IWL_DBG_CFG_H_
 
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
 /*
  * with DBG_CFG_REINCLUDE set this file should contain nothing
  * but IWL_DBG_CFG() macro invocations so it can be used in
diff --git a/third_party/iwlwifi/iwl-drv.c b/third_party/iwlwifi/iwl-drv.c
index 3efb4d0..32b13ad 100644
--- a/third_party/iwlwifi/iwl-drv.c
+++ b/third_party/iwlwifi/iwl-drv.c
@@ -33,7 +33,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *****************************************************************************/
-#include "iwl-drv.h"
+#include "third_party/iwlwifi/iwl-drv.h"
 
 #include <lib/ddk/debug.h>
 #include <lib/ddk/driver.h>
@@ -43,7 +43,6 @@
 #include <zircon/listnode.h>
 #include <zircon/status.h>
 
-#include "third_party/iwlwifi/platform/align.h"
 #include "third_party/iwlwifi/fw/img.h"
 #include "third_party/iwlwifi/iwl-agn-hw.h"
 #include "third_party/iwlwifi/iwl-config.h"
@@ -53,13 +52,14 @@
 #include "third_party/iwlwifi/iwl-modparams.h"
 #include "third_party/iwlwifi/iwl-op-mode.h"
 #include "third_party/iwlwifi/iwl-trans.h"
+#include "third_party/iwlwifi/platform/align.h"
 #include "third_party/iwlwifi/platform/device.h"
 #include "third_party/iwlwifi/platform/module.h"
 #ifdef CPTCFG_IWLWIFI_SUPPORT_DEBUG_OVERRIDES
-#include "iwl-dbg-cfg.h"
+#include "third_party/iwlwifi/iwl-dbg-cfg.h"
 #endif
 #ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE
-#include "iwl-tm-gnl.h"
+#include "third_party/iwlwifi/iwl-tm-gnl.h"
 #endif
 
 #define FIRMWARE_DIR "iwlwifi"
diff --git a/third_party/iwlwifi/iwl-eeprom-parse.c b/third_party/iwlwifi/iwl-eeprom-parse.c
index 7cf77e4..049f94e 100644
--- a/third_party/iwlwifi/iwl-eeprom-parse.c
+++ b/third_party/iwlwifi/iwl-eeprom-parse.c
@@ -682,12 +682,11 @@
   return n;
 }
 
-#if 0                           // NEEDS_PORTING
 #define MAX_BIT_RATE_40_MHZ 150 /* Mbps */
 #define MAX_BIT_RATE_20_MHZ 72  /* Mbps */
 
 void iwl_init_ht_hw_capab(const struct iwl_cfg* cfg, struct iwl_nvm_data* data,
-                          struct ieee80211_sta_ht_cap* ht_info, enum nl80211_band band,
+                          struct ieee80211_sta_ht_cap* ht_info, wlan_info_band_t band,
                           uint8_t tx_chains, uint8_t rx_chains) {
   int max_bit_rate = 0;
 
@@ -754,8 +753,8 @@
 
   /* Highest supported Rx data rate */
   max_bit_rate *= rx_chains;
-  WARN_ON(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK);
-  ht_info->mcs.rx_highest = cpu_to_le16(max_bit_rate);
+  ZX_ASSERT(~(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK));
+  ht_info->mcs.rx_highest_le = cpu_to_le16(max_bit_rate);
 
   /* Tx MCS capabilities */
   ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
@@ -765,6 +764,8 @@
   }
 }
 
+#if 0   // NEEDS_PORTING
+
 static void iwl_init_sbands(struct device* dev, const struct iwl_cfg* cfg,
                             struct iwl_nvm_data* data, const uint8_t* eeprom, size_t eeprom_size) {
   int n_channels = iwl_init_channel_map(dev, cfg, data, eeprom, eeprom_size);
@@ -881,4 +882,4 @@
   return NULL;
 }
 IWL_EXPORT_SYMBOL(iwl_parse_eeprom_data);
-#endif                          // NEEDS_PORTING
+#endif  // NEEDS_PORTING
diff --git a/third_party/iwlwifi/iwl-eeprom-parse.h b/third_party/iwlwifi/iwl-eeprom-parse.h
index d8041c3..62d8b6a 100644
--- a/third_party/iwlwifi/iwl-eeprom-parse.h
+++ b/third_party/iwlwifi/iwl-eeprom-parse.h
@@ -34,7 +34,7 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_IWL_EEPROM_PARSE_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_IWL_EEPROM_PARSE_H_
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
 
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/platform/compiler.h"
diff --git a/third_party/iwlwifi/iwl-nvm-parse.c b/third_party/iwlwifi/iwl-nvm-parse.c
index e4b9e88..9a19eaa 100644
--- a/third_party/iwlwifi/iwl-nvm-parse.c
+++ b/third_party/iwlwifi/iwl-nvm-parse.c
@@ -46,7 +46,7 @@
 #include "third_party/iwlwifi/iwl-io.h"
 #include "third_party/iwlwifi/iwl-modparams.h"
 #include "third_party/iwlwifi/iwl-prph.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 /* NVM offsets (in words) definitions */
 enum nvm_offsets {
@@ -327,7 +327,7 @@
     n_channels++;
 
     channel->ch_num = nvm_chan[ch_idx];
-    channel->band = is_5ghz ? WLAN_INFO_BAND_5GHZ : WLAN_INFO_BAND_2GHZ;
+    channel->band = is_5ghz ? WLAN_INFO_BAND_FIVE_GHZ : WLAN_INFO_BAND_TWO_GHZ;
     channel->center_freq = ieee80211_get_center_freq((uint8_t)channel->ch_num);
 
     /* Initialize regulatory-based run-time data */
@@ -739,32 +739,36 @@
   struct ieee80211_supported_band* sband;
 
   n_channels = iwl_init_channel_map(dev, cfg, data, nvm_ch_flags, sbands_flags);
-  sband = &data->bands[WLAN_INFO_BAND_2GHZ];
-  sband->band = WLAN_INFO_BAND_2GHZ;
+  sband = &data->bands[WLAN_INFO_BAND_TWO_GHZ];
+  sband->band = WLAN_INFO_BAND_TWO_GHZ;
   sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS];
   sband->n_bitrates = N_RATES_24;
-  n_used += iwl_init_sband_channels(data, sband, n_channels, WLAN_INFO_BAND_2GHZ);
-#if 0   // NEEDS_PORTING
-  // TODO(36683): HT support.
-  iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, NL80211_BAND_2GHZ, tx_chains, rx_chains);
+  n_used += iwl_init_sband_channels(data, sband, n_channels, WLAN_INFO_BAND_TWO_GHZ);
 
+  iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, WLAN_INFO_BAND_TWO_GHZ, tx_chains, rx_chains);
+
+#if 0   // NEEDS_PORTING
+  // TODO(84773): HE support.
   if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) {
     iwl_init_he_hw_capab(sband, tx_chains, rx_chains);
   }
 #endif  // NEEDS_PORTING
 
-  sband = &data->bands[WLAN_INFO_BAND_5GHZ];
-  sband->band = WLAN_INFO_BAND_5GHZ;
+  sband = &data->bands[WLAN_INFO_BAND_FIVE_GHZ];
+  sband->band = WLAN_INFO_BAND_FIVE_GHZ;
   sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS];
   sband->n_bitrates = N_RATES_52;
-  n_used += iwl_init_sband_channels(data, sband, n_channels, WLAN_INFO_BAND_5GHZ);
+  n_used += iwl_init_sband_channels(data, sband, n_channels, WLAN_INFO_BAND_FIVE_GHZ);
+
+  iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, WLAN_INFO_BAND_FIVE_GHZ, tx_chains, rx_chains);
+
 #if 0   // NEEDS_PORTING
-  // TODO(36683): HT support.
-  iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, NL80211_BAND_5GHZ, tx_chains, rx_chains);
+  // TODO(36684): Supports VHT (802.11ac)
   if (data->sku_cap_11ac_enable && !iwlwifi_mod_params.disable_11ac) {
     iwl_init_vht_hw_capab(trans, data, &sband->vht_cap, tx_chains, rx_chains);
   }
 
+  // TODO(84773): HE support.
   if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) {
     iwl_init_he_hw_capab(sband, tx_chains, rx_chains);
   }
@@ -1058,8 +1062,8 @@
   }
 
 #ifdef CPTCFG_IWLWIFI_SUPPORT_DEBUG_OVERRIDES
-  iwl_init_he_override(trans, &data->bands[WLAN_INFO_BAND_2GHZ]);
-  iwl_init_he_override(trans, &data->bands[WLAN_INFO_BAND_5GHZ]);
+  iwl_init_he_override(trans, &data->bands[WLAN_INFO_BAND_TWO_GHZ]);
+  iwl_init_he_override(trans, &data->bands[WLAN_INFO_BAND_FIVE_GHZ]);
 #endif
   if (lar_fw_supported && lar_enabled) {
     sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR;
diff --git a/third_party/iwlwifi/iwl-trans.c b/third_party/iwlwifi/iwl-trans.c
index caeadc0..c1a78d1 100644
--- a/third_party/iwlwifi/iwl-trans.c
+++ b/third_party/iwlwifi/iwl-trans.c
@@ -87,7 +87,8 @@
 
   ret = trans->ops->send_cmd(trans, cmd);
 
-  if (WARN_ON((cmd->flags & CMD_WANT_SKB) && !ret && !cmd->resp_pkt)) {
+  if ((cmd->flags & CMD_WANT_SKB) && !ret && !cmd->resp_pkt) {
+    IWL_ERR(trans, "%s(): WANT_SKB required but no resp pkt attached.\n", __func__);
     return ZX_ERR_IO;
   }
 
diff --git a/third_party/iwlwifi/iwl-trans.h b/third_party/iwlwifi/iwl-trans.h
index 774cf62..88ef902 100644
--- a/third_party/iwlwifi/iwl-trans.h
+++ b/third_party/iwlwifi/iwl-trans.h
@@ -36,6 +36,10 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_IWL_TRANS_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_IWL_TRANS_H_
 
+// ToDo(rsakthi): war sinde defines isnt working in bazel.
+#define CPTCFG_IWLMVM 1
+#define CPTCFG_IWL_TIMEOUT_FACTOR 1
+
 #include "third_party/iwlwifi/fw/img.h"
 #include "third_party/iwlwifi/iwl-config.h"
 #include "third_party/iwlwifi/iwl-debug.h"
diff --git a/third_party/iwlwifi/iwlwifi_driver-info.json b/third_party/iwlwifi/iwlwifi_driver-info.json
new file mode 100644
index 0000000..a80c124
--- /dev/null
+++ b/third_party/iwlwifi/iwlwifi_driver-info.json
@@ -0,0 +1,16 @@
+{
+    "short_description": "Intel WLAN SoftMAC driver",
+    "manufacturer": "Intel",
+    "families": [
+        "iwlwifi"
+    ],
+    "models": [
+        "7265",
+        "8265",
+        "9260"
+    ],
+    "areas": [
+        "Connectivity",
+        "Wlan"
+    ]
+}
diff --git a/third_party/iwlwifi/mvm/API_rates.h b/third_party/iwlwifi/mvm/API_rates.h
index 7edd932..c8884dc 100644
--- a/third_party/iwlwifi/mvm/API_rates.h
+++ b/third_party/iwlwifi/mvm/API_rates.h
@@ -408,14 +408,14 @@
 // bit-13 0==>normal guard interval 1==>short guard interval
 #define RATE_MCS_SGI_POS 13
 #define RATE_MCS_SGI_MSK (1 << RATE_MCS_SGI_POS)
-// bit-14 0==>chain A incative 1==>chain A active
+// bit-14 0==>chain A inactive 1==>chain A active
 #define RATE_MCS_ANT_A_POS 14
-// bit-15 0==>chain B incative 1==>chain B active
+// bit-15 0==>chain B inactive 1==>chain B active
 #define RATE_MCS_ANT_B_POS 15
 
 // new flags in shiloh (ext_flags)
 
-// bit-16 0==>chain B incative 1==>chain B active
+// bit-16 0==>chain B inactive 1==>chain B active
 #define RATE_MCS_ANT_C_POS 16
 // bit-15:14 mask for both ant.
 #define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | RATE_MCS_ANT_B_MSK)
@@ -904,10 +904,10 @@
 #define GET_HT_VHT_HE_RATE_CODE_API_M(c_rate) GET_HT_VHT_RATE_CODE_API_M_VER_1(c_rate)
 
 // vht supported
-#define GET_MIMO_INDEX_API_M_VER_3(c_rate)     \
-  (IS_RATE_OFDM_HT_API_M_VER_2(c_rate)         \
-       ? GET_HT_MIMO_INDEX_API_M_VER_1(c_rate) \
-       : IS_RATE_OFDM_VHT_HE_API_M(c_rate) ? GET_VHT_MIMO_INDX_API_M_VER_1(c_rate) : SISO_INDX)
+#define GET_MIMO_INDEX_API_M_VER_3(c_rate)                                     \
+  (IS_RATE_OFDM_HT_API_M_VER_2(c_rate) ? GET_HT_MIMO_INDEX_API_M_VER_1(c_rate) \
+   : IS_RATE_OFDM_VHT_HE_API_M(c_rate) ? GET_VHT_MIMO_INDX_API_M_VER_1(c_rate) \
+                                       : SISO_INDX)
 
 /**@} GroupRates */
 
diff --git a/third_party/iwlwifi/mvm/BUILD.bazel b/third_party/iwlwifi/mvm/BUILD.bazel
index 2e98f2d..8a9a5dc 100644
--- a/third_party/iwlwifi/mvm/BUILD.bazel
+++ b/third_party/iwlwifi/mvm/BUILD.bazel
@@ -16,6 +16,8 @@
     "ops.c",
     "phy-ctxt.c",
     "power.c",
+    "rateScaleMng.c",
+    "rs-ng.c",
     "rx.c",
     "rxmq.c",
     "scan.c",
@@ -41,17 +43,14 @@
    "tof.h",
   ],
   deps = [
-  #  "//zircon/system/ulib/zircon-internal",
-  #  "//garnet/lib/wlan/protocol:protocol",
-    "@fuchsia_sdk//fidl/fuchsia_hardware_wlanphyinfo:fuchsia_hardware_wlanphyinfo_banjo_cc",
-    "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_info:fuchsia_hardware_wlan_info_banjo_cc",
+    "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_phyinfo:fuchsia_hardware_wlan_phyinfo_banjo_cc",
+    "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_associnfo:fuchsia_hardware_wlan_associnfo_banjo_cc",
+    "@fuchsia_sdk//fidl/fuchsia_hardware_wlanphyimpl:fuchsia_hardware_wlanphyimpl_banjo_cc",
     "@fuchsia_sdk//fidl/fuchsia_wlan_ieee80211:fuchsia_wlan_ieee80211_llcpp_cc",
     "//third_party/iwlwifi:core",
     "//third_party/iwlwifi/fw:fw",
     "//third_party/iwlwifi/fw:api",
-   "//third_party/iwlwifi/platform:platform",
+    "//third_party/iwlwifi/platform:platform",
     "@fuchsia_sdk//pkg/ddk",
-#    "//zircon/system/public",
-#    "//zircon/system/ulib/async",
   ],
 )
diff --git a/third_party/iwlwifi/mvm/BUILD.gn b/third_party/iwlwifi/mvm/BUILD.gn
index cf10c18..b11247f 100644
--- a/third_party/iwlwifi/mvm/BUILD.gn
+++ b/third_party/iwlwifi/mvm/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-visibility = [ "//src/iwlwifi/*" ]
+visibility = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/*" ]
 
 source_set("mvm") {
   sources = [
@@ -15,6 +15,8 @@
     "ops.c",
     "phy-ctxt.c",
     "power.c",
+    "rateScaleMng.c",
+    "rs-ng.c",
     "rx.c",
     "rxmq.c",
     "scan.c",
@@ -39,16 +41,14 @@
     "time-event.h",
     "tof.h",
   ]
-  deps = [ "//zircon/system/ulib/zircon-internal" ]
   public_deps = [
-    "//garnet/lib/wlan/protocol:protocol",
-    "//sdk/banjo/ddk.hw.wlan.wlaninfo:ddk.hw.wlan.wlaninfo_banjo_c",
-    "//sdk/banjo/fuchsia.hardware.wlan.info:fuchsia.hardware.wlan.info_banjo_c",
+    "//sdk/banjo/fuchsia.hardware.wlan.associnfo:fuchsia.hardware.wlan.associnfo_banjo_c",
+    "//sdk/banjo/fuchsia.hardware.wlan.phyinfo:fuchsia.hardware.wlan.phyinfo_banjo_c",
     "//sdk/fidl/fuchsia.wlan.ieee80211:fuchsia.wlan.ieee80211_c",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/fw",
-    "//src/iwlwifi/fw:api",
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw:api",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//src/lib/ddk",
     "//zircon/system/public",
     "//zircon/system/ulib/async",
diff --git a/third_party/iwlwifi/mvm/_rateScaleMng.h b/third_party/iwlwifi/mvm/_rateScaleMng.h
index a4214f4..3ebaefe 100644
--- a/third_party/iwlwifi/mvm/_rateScaleMng.h
+++ b/third_party/iwlwifi/mvm/_rateScaleMng.h
@@ -36,8 +36,12 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM__RATESCALEMNG_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM__RATESCALEMNG_H_
 
+#include "third_party/iwlwifi/iwl-config.h"
+#include "third_party/iwlwifi/mvm/API_rates.h"
 #include "third_party/iwlwifi/mvm/apiGroupDatapath.h"
 #include "third_party/iwlwifi/mvm/apiVersion.h"
+#include "third_party/iwlwifi/platform/compiler.h"
+#include "third_party/iwlwifi/platform/debug.h"
 
 #define RS_MNG_INVALID_VAL ((U32)-1)
 #define RS_MNG_RATE_MIN_FAILURE_TH 3
@@ -135,11 +139,11 @@
 };
 
 typedef struct _RS_MNG_STA_LIMITS_S {
-  U32 successFramesLimit;    // successfull frames threshold for starting a search cycle.
-  U32 failedFramesLimit;     // failed frames threshold for starting a search cycle.
-  U32 statsFlushTimeLimit;   // time thrshold for starting a search cycle, in usec.
-  U32 clearTblWindowsLimit;  // txed frames threshold for clearing table windows during
-                             // stay-in-col.
+  uint32_t successFramesLimit;    // successfull frames threshold for starting a search cycle.
+  uint32_t failedFramesLimit;     // failed frames threshold for starting a search cycle.
+  uint32_t statsFlushTimeLimit;   // time thrshold for starting a search cycle, in usec.
+  uint32_t clearTblWindowsLimit;  // txed frames threshold for clearing table windows during
+                                  // stay-in-col.
 } RS_MNG_STA_LIMITS_S;
 
 // TX AMSDU size
@@ -217,7 +221,7 @@
   RS_MCS_NUM,
 } RS_MCS_E;
 
-#define RS_MNG_MAX_RATES_NUM MAX((U08)RS_NON_HT_RATE_NUM, (U08)RS_MCS_NUM)
+#define RS_MNG_MAX_RATES_NUM MAX((uint8_t)RS_NON_HT_RATE_NUM, (uint8_t)RS_MCS_NUM)
 
 typedef enum _RS_MNG_STATE_E {
   RS_MNG_STATE_SEARCH_CYCLE_STARTED,
@@ -255,7 +259,7 @@
 } RS_MNG_COLUMN_DESC_E;
 
 /***********************************/
-typedef U16 TPT_BY_RATE_ARR[RS_MNG_MAX_RATES_NUM];
+typedef uint16_t TPT_BY_RATE_ARR[RS_MNG_MAX_RATES_NUM];
 
 /**************************************************/
 
@@ -329,14 +333,14 @@
   U08 ignoreNextTlcNotif;                // The next notification recieved from lmac is irrelevant.
   // Could happen if aggregations are opened in the middle of a
   // search cycle.
-  U08 tryingRateUpscale;           // TRUE if now trying to upscale the rate.
-  U32 lastRateUpscaleTimeJiffies;  // system time of last rate upscale attempt.
-  U32 totalFramesFailed;           // total failed frames, any/all rates //total_failed
+  U08 tryingRateUpscale;               // TRUE if now trying to upscale the rate.
+  zx_time_t lastRateUpscaleTimestamp;  // system time of last rate upscale attempt.
+  U32 totalFramesFailed;               // total failed frames, any/all rates //total_failed
   U32 totalFramesSuccess;
   U16 framesSinceLastRun;  // number of frames sent since the last time rateScalePerform
   // ran.
-  U32 lastSearchCycleEndTimeJiffies;  // time since end of last search cycle
-  U32 txedFrames;                     // number of txed frames while stay in column, before clearing
+  zx_time_t lastSearchCycleEndTimestamp;  // time since end of last search cycle
+  U32 txedFrames;  // number of txed frames while stay in column, before clearing
   // the all the stat windows in the current table.
   U32 visitedColumns;  // bitmask of TX columns that were tested during this search cycle
   U32 searchBw;        // holds a new bandwidth to try before ending a search cycle,
@@ -352,11 +356,11 @@
   RS_MNG_TX_AMSDU_SIZE_E amsduEnabledSize;
   U32 trafficLoad;
   U08 amsduBlacklist;
-  U32 lastTrafficLoadStatJiffies;
+  zx_time_t lastTrafficLoadStatTimestamp;
   U32 failSafeCounter;
   bool isUpscaleSearchCycle;  // TRUE if last search cycle started because of passing success
   // frame limit.
-  U32 lastEnableJiffies;  // timestamp of the last TX AMSDU enablement
+  zx_time_t lastEnableTimestamp;  // timestamp of the last TX AMSDU enablement
 
   RATE_MCS_API_U lastNotifiedRate;
 
@@ -385,12 +389,14 @@
   ALLOW_COL_FUNC_F checks[MAX_COLUMN_CHECKS];
 };
 
+uint64_t nonht_rate_to_bit(uint8_t rate_value);
+
 static INLINE U08 rsMngGetDualAntMsk(void) { return TLC_MNG_CHAIN_A_MSK | TLC_MNG_CHAIN_B_MSK; }
 
 static INLINE U08 _rsMngGetSingleAntMsk(U08 chainsEnabled, uint8_t non_shared_ant,
                                         uint8_t valid_tx_ant) {
-  BUILD_BUG_ON(TLC_MNG_CHAIN_A_MSK != ANT_A);
-  BUILD_BUG_ON(TLC_MNG_CHAIN_B_MSK != ANT_B);
+  ZX_ASSERT(TLC_MNG_CHAIN_A_MSK == ANT_A);
+  ZX_ASSERT(TLC_MNG_CHAIN_B_MSK == ANT_B);
   // Since TLC offload only supports 2 chains, if the non-shared antenna isn't enabled,
   // chainsEnabled must have exactly one chain enabled.
   return (U08)(valid_tx_ant != rsMngGetDualAntMsk()
@@ -398,6 +404,10 @@
                    : (non_shared_ant & chainsEnabled ? non_shared_ant : chainsEnabled));
 }
 
+void cmdHandlerTlcMngConfig(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta,
+                            RS_MNG_STA_INFO_S* staInfo, TLC_MNG_CONFIG_PARAMS_CMD_API_S* config,
+                            bool reconfigure);
+
 #define rsMngGetSingleAntMsk(chainsEnabled)                                  \
   (_rsMngGetSingleAntMsk((chainsEnabled), staInfo->mvm->cfg->non_shared_ant, \
                          iwl_mvm_get_valid_tx_ant(staInfo->mvm)))
diff --git a/third_party/iwlwifi/mvm/apiGroupDatapath.h b/third_party/iwlwifi/mvm/apiGroupDatapath.h
index 9ff3e5b..1ffe9d4 100644
--- a/third_party/iwlwifi/mvm/apiGroupDatapath.h
+++ b/third_party/iwlwifi/mvm/apiGroupDatapath.h
@@ -44,7 +44,7 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_APIGROUPDATAPATH_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_APIGROUPDATAPATH_H_
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
 
 /* ***************************************************************************
  * 0x0F - GRP_DATAPATH_TLC_MNG_CONFIG_CMD
@@ -140,13 +140,13 @@
 #define TLC_AMSDU_SUPPORTED 1
 
 typedef struct _TLC_MNG_CONFIG_PARAMS_CMD_API_S_VER_2 {
-  U08 maxChWidth;      // one of TLC_MNG_CH_WIDTH_E
-  U08 bestSuppMode;    // best mode supported - as defined above in TLC_MNG_MODE_E
-  U08 chainsEnabled;   // bitmask of TLC_MNG_CHAIN_[A/B]_MSK
-  U08 amsduSupported;  // TX AMSDU transmission is supported
+  uint8_t maxChWidth;      // one of TLC_MNG_CH_WIDTH_E
+  uint8_t bestSuppMode;    // best mode supported - as defined above in TLC_MNG_MODE_E
+  uint8_t chainsEnabled;   // bitmask of TLC_MNG_CHAIN_[A/B]_MSK
+  uint8_t amsduSupported;  // TX AMSDU transmission is supported
   // Use TLC_AMSDU_[NOT_]SUPPORTED
-  U16 configFlags;  // bitmask of TLC_MNG_CONFIG_FLAGS_*
-  U16 nonHt;        // bitmap of supported non-HT CCK and OFDM rates
+  uint16_t configFlags;  // bitmask of TLC_MNG_CONFIG_FLAGS_*
+  uint16_t nonHt;        // bitmap of supported non-HT CCK and OFDM rates
   /* bit   | rate
      -------|--------
      0    | R_1M   CCK
@@ -162,16 +162,16 @@
      10   | R_48M  OFDM
      11   | R_54M  OFDM
      */
-  U16 mcs[TLC_MNG_NSS_MAX][2];  // supported HT/VHT/HE rates per nss. [0] for 80mhz width
+  uint16_t mcs[TLC_MNG_NSS_MAX][2];  // supported HT/VHT/HE rates per nss. [0] for 80mhz width
   // and lower, [1] for 160mhz.
   // This is done in order to conform with HE capabilites.
-  U16 maxMpduLen;  // Max length of MPDU, in bytes.
+  uint16_t maxMpduLen;  // Max length of MPDU, in bytes.
   // Used to calculate allowed A-MSDU sizes.
-  U08 sgiChWidthSupport;  // bitmap of SGI support per channel width.
+  uint8_t sgiChWidthSupport;  // bitmap of SGI support per channel width.
   // use 1 << BIT(TLC_MNG_CH_WIDTH_*) to indicate sgi support
   // for that channel width.
   // unused for HE.
-  U08 reserved1[1];
+  uint8_t reserved1[1];
 
   wlan_info_band_t band;
 } TLC_MNG_CONFIG_PARAMS_CMD_API_S_VER_2;
diff --git a/third_party/iwlwifi/mvm/binding.c b/third_party/iwlwifi/mvm/binding.c
index b68ac79..16eda6e 100644
--- a/third_party/iwlwifi/mvm/binding.c
+++ b/third_party/iwlwifi/mvm/binding.c
@@ -59,7 +59,7 @@
 
   if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT)) {
     size = sizeof(cmd);
-    if (iwl_mvm_get_channel_band(phyctxt->chandef.primary) == WLAN_INFO_BAND_2GHZ ||
+    if (iwl_mvm_get_channel_band(phyctxt->chandef.primary) == WLAN_INFO_BAND_TWO_GHZ ||
         !iwl_mvm_is_cdb_supported(mvm)) {
       cmd.lmac_id = cpu_to_le32(IWL_LMAC_24G_INDEX);
     } else {
diff --git a/third_party/iwlwifi/mvm/fw.c b/third_party/iwlwifi/mvm/fw.c
index 3888e8f..ccd395f 100644
--- a/third_party/iwlwifi/mvm/fw.c
+++ b/third_party/iwlwifi/mvm/fw.c
@@ -1235,17 +1235,16 @@
     goto error;
   }
 
-#if 0   // NEEDS_PORTING
-    /*
-    * RTNL is not taken during Ct-kill, but we don't need to scan/Tx
-    * anyway, so don't init MCC.
-    */
-    // TODO(42213): port this function.
-    if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) {
-        ret = iwl_mvm_init_mcc(mvm);
-        if (ret) { goto error; }
+  /*
+   * RTNL is not taken during Ct-kill, but we don't need to scan/Tx
+   * anyway, so don't init MCC.
+   */
+  if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) {
+    ret = iwl_mvm_init_mcc(mvm);
+    if (ret != ZX_OK) {
+      goto error;
     }
-#endif  // NEEDS_PORTING
+  }
 
   if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) {
     mvm->scan_type = IWL_SCAN_TYPE_NOT_SET;
diff --git a/third_party/iwlwifi/mvm/mac-ctxt.c b/third_party/iwlwifi/mvm/mac-ctxt.c
index b0be0e1..528bcfd 100644
--- a/third_party/iwlwifi/mvm/mac-ctxt.c
+++ b/third_party/iwlwifi/mvm/mac-ctxt.c
@@ -39,6 +39,7 @@
 #include "third_party/iwlwifi/mvm/fw-api.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
 #include "third_party/iwlwifi/mvm/time-event.h"
+#include "third_party/iwlwifi/platform/rcu.h"
 
 const uint8_t iwl_mvm_ac_to_tx_fifo[] = {
     IWL_MVM_TX_FIFO_VO,
@@ -511,10 +512,10 @@
   cmd->action = cpu_to_le32(action);
 
   switch (mvmvif->mac_role) {
-    case WLAN_INFO_MAC_ROLE_CLIENT:
+    case WLAN_MAC_ROLE_CLIENT:
       cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_BSS_STA);
       break;
-    case WLAN_INFO_MAC_ROLE_AP:
+    case WLAN_MAC_ROLE_AP:
       cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_GO);
       break;
 #if 0   // NEEDS_PORTING
@@ -542,9 +543,9 @@
     eth_broadcast_addr(cmd->bssid_addr);
   }
 
-  rcu_read_lock();
+  iwl_rcu_read_lock(mvm->dev);
   iwl_mvm_ack_rates(mvmvif, band, &cck_ack_rates, &ofdm_ack_rates);
-  rcu_read_unlock();
+  iwl_rcu_read_unlock(mvm->dev);
 
   cmd->cck_rates = cpu_to_le32((uint32_t)cck_ack_rates);
   cmd->ofdm_rates = cpu_to_le32((uint32_t)ofdm_ack_rates);
@@ -597,10 +598,10 @@
   struct iwl_mac_ctx_cmd cmd = {};
   struct iwl_mac_data_sta* ctxt_sta;
 
-  WARN_ON(mvmvif->mac_role != WLAN_INFO_MAC_ROLE_CLIENT);
+  WARN_ON(mvmvif->mac_role != WLAN_MAC_ROLE_CLIENT);
 
   /* Fill the common data for all mac context types */
-  iwl_mvm_mac_ctxt_cmd_common(mvmvif, WLAN_INFO_BAND_2GHZ,  // Use default value.
+  iwl_mvm_mac_ctxt_cmd_common(mvmvif, WLAN_INFO_BAND_TWO_GHZ,  // Use default value.
                               mvmvif->ht_enabled, &cmd, bssid_override, action);
 
 #if 1  // NEEDS_PORTING
@@ -1122,7 +1123,7 @@
 static zx_status_t iwl_mvm_mac_ctx_send(struct iwl_mvm_vif* mvmvif, uint32_t action,
                                         bool force_assoc_off, const uint8_t* bssid_override) {
   switch (mvmvif->mac_role) {
-    case WLAN_INFO_MAC_ROLE_CLIENT:
+    case WLAN_MAC_ROLE_CLIENT:
       return iwl_mvm_mac_ctxt_cmd_sta(mvmvif, action, force_assoc_off, bssid_override);
       break;
 #if 0   // NEEDS_PORTING
diff --git a/third_party/iwlwifi/mvm/mac80211.c b/third_party/iwlwifi/mvm/mac80211.c
index 1dd56ea..a398583 100644
--- a/third_party/iwlwifi/mvm/mac80211.c
+++ b/third_party/iwlwifi/mvm/mac80211.c
@@ -34,7 +34,7 @@
  *
  *****************************************************************************/
 
-#include <fuchsia/hardware/wlan/info/c/banjo.h>
+#include <fuchsia/hardware/wlan/associnfo/c/banjo.h>
 #include <string.h>
 #include <zircon/status.h>
 
@@ -53,7 +53,8 @@
 #include "third_party/iwlwifi/mvm/sta.h"
 #include "third_party/iwlwifi/mvm/time-event.h"
 #include "third_party/iwlwifi/mvm/tof.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/rcu.h"
 #ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE
 #include "third_party/iwlwifi/iwl-dnt-cfg.h"
 #include "third_party/iwlwifi/iwl-dnt-dispatch.h"
@@ -284,50 +285,60 @@
   }
 }
 
-#if 0  // NEEDS_PORTING
-struct ieee80211_regdomain* iwl_mvm_get_regdomain(struct wiphy* wiphy, const char* alpha2,
-                                                  enum iwl_mcc_source src_id, bool* changed) {
-    struct ieee80211_regdomain* regd = NULL;
-    struct ieee80211_hw* hw = wiphy_to_ieee80211_hw(wiphy);
-    struct iwl_mvm* mvm = IWL_MAC80211_GET_MVM(hw);
-    struct iwl_mcc_update_resp* resp;
+zx_status_t iwl_mvm_get_regdomain(struct iwl_mvm* mvm, const char* alpha2,
+                                  enum iwl_mcc_source src_id, bool* changed,
+                                  wlanphy_country_t* out_country) {
+  wlanphy_country_t country = {};
+  wlanphy_country_t* regd = &country;
+  struct iwl_mcc_update_resp* resp;
 
-    IWL_DEBUG_LAR(mvm, "Getting regdomain data for %s from FW\n", alpha2);
+  ZX_ASSERT(out_country);
 
-    iwl_assert_lock_held(&mvm->mutex);
+  IWL_DEBUG_LAR(mvm, "Getting regdomain data for %s from FW\n", alpha2);
 
-    resp = iwl_mvm_update_mcc(mvm, alpha2, src_id);
-    if (IS_ERR_OR_NULL(resp)) {
-        IWL_DEBUG_LAR(mvm, "Could not get update from FW %d\n", PTR_ERR_OR_ZERO(resp));
-        goto out;
-    }
+  iwl_assert_lock_held(&mvm->mutex);
 
-    if (changed) {
-        uint32_t status = le32_to_cpu(resp->status);
+  zx_status_t ret = iwl_mvm_update_mcc(mvm, alpha2, src_id, &resp);
+  if (ret != ZX_OK) {
+    IWL_DEBUG_LAR(mvm, "Could not get update from FW %s\n", zx_status_get_string(ret));
+    goto out;
+  }
 
-        *changed = (status == MCC_RESP_NEW_CHAN_PROFILE || status == MCC_RESP_ILLEGAL);
-    }
+  if (changed) {
+    uint32_t status = le32_to_cpu(resp->status);
 
-    regd = iwl_parse_nvm_mcc_info(mvm->trans->dev, mvm->cfg, __le32_to_cpu(resp->n_channels),
-                                  resp->channels, __le16_to_cpu(resp->mcc),
-                                  __le16_to_cpu(resp->geo_info));
-    /* Store the return source id */
-    src_id = resp->source_id;
-    kfree(resp);
-    if (IS_ERR_OR_NULL(regd)) {
-        IWL_DEBUG_LAR(mvm, "Could not get parse update from FW %d\n", PTR_ERR_OR_ZERO(regd));
-        goto out;
-    }
+    *changed = (status == MCC_RESP_NEW_CHAN_PROFILE || status == MCC_RESP_ILLEGAL);
+  }
 
-    IWL_DEBUG_LAR(mvm, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n", regd->alpha2,
-                  regd->alpha2[0], regd->alpha2[1], src_id);
-    mvm->lar_regdom_set = true;
-    mvm->mcc_src = src_id;
+#if 1  // NEEDS_PORTING
+  country.alpha2[0] = le16_to_cpu(resp->mcc) >> 8;
+  country.alpha2[1] = le16_to_cpu(resp->mcc) & 0xff;
+  *out_country = *regd;
+#else   // NEEDS_PORTING
+  // TODO(fxbug.dev/87321): port iwl_parse_nvm_mcc_info()
+  struct ieee80211_regdomain* regd = iwl_parse_nvm_mcc_info(
+      mvm->trans->dev, mvm->cfg, __le32_to_cpu(resp->n_channels), resp->channels,
+      __le16_to_cpu(resp->mcc), __le16_to_cpu(resp->geo_info));
+#endif  // NEEDS_PORTING
+
+  /* Store the return source id */
+  src_id = resp->source_id;
+  free(resp);
+  if (ret != ZX_OK) {
+    IWL_DEBUG_LAR(mvm, "Could not get parse update from FW: %s\n", zx_status_get_string(ret));
+    goto out;
+  }
+
+  IWL_DEBUG_LAR(mvm, "setting alpha2 from FW to %c%c (0x%x, 0x%x) src=%d\n", regd->alpha2[0],
+                regd->alpha2[1], regd->alpha2[0], regd->alpha2[1], src_id);
+  mvm->lar_regdom_set = true;
+  mvm->mcc_src = src_id;
 
 out:
-    return regd;
+  return ret;
 }
 
+#if 0   // NEEDS_PORTING
 void iwl_mvm_update_changed_regdom(struct iwl_mvm* mvm) {
     bool changed;
     struct ieee80211_regdomain* regd;
@@ -342,13 +353,16 @@
         kfree(regd);
     }
 }
+#endif  // NEEDS_PORTING
 
-struct ieee80211_regdomain* iwl_mvm_get_current_regdomain(struct iwl_mvm* mvm, bool* changed) {
-    return iwl_mvm_get_regdomain(
-        mvm->hw->wiphy, "ZZ",
-        iwl_mvm_is_wifi_mcc_supported(mvm) ? MCC_SOURCE_GET_CURRENT : MCC_SOURCE_OLD_FW, changed);
+zx_status_t iwl_mvm_get_current_regdomain(struct iwl_mvm* mvm, bool* changed,
+                                          wlanphy_country_t* out_country) {
+  return iwl_mvm_get_regdomain(
+      mvm, "ZZ", iwl_mvm_is_wifi_mcc_supported(mvm) ? MCC_SOURCE_GET_CURRENT : MCC_SOURCE_OLD_FW,
+      changed, out_country);
 }
 
+#if 0  // NEEDS_PORTING
 int iwl_mvm_init_fw_regd(struct iwl_mvm* mvm) {
     enum iwl_mcc_source used_src;
     struct ieee80211_regdomain* regd;
@@ -573,20 +587,15 @@
 }
 #endif  // NEEDS_PORTING
 
-zx_status_t iwl_mvm_mac_tx(struct iwl_mvm_vif* mvmvif, struct ieee80211_mac_packet* pkt) {
+zx_status_t iwl_mvm_mac_tx(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
+                           struct ieee80211_mac_packet* pkt) {
   iwl_assert_lock_held(&mvmvif->mvm->mutex);
 
-  if (mvmvif->mac_role != WLAN_INFO_MAC_ROLE_CLIENT) {
+  if (mvmvif->mac_role != WLAN_MAC_ROLE_CLIENT) {
     IWL_ERR(mvmvif, "%s(): not supported MAC role %d yet\n", __func__, mvmvif->mac_role);
     return ZX_ERR_INVALID_ARGS;
   }
 
-  struct iwl_mvm_sta* mvmsta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id];
-  if (!mvmsta) {
-    IWL_ERR(mvmvif, "%s(): mvmsta is NULL. mvmvif->ap_sta_id=%d\n", __func__, mvmvif->ap_sta_id);
-    return ZX_ERR_INTERNAL;
-  }
-
   return iwl_mvm_tx_skb(mvmvif->mvm, pkt, mvmsta);
 
 #if 0   // NEEDS_PORTING
@@ -1451,6 +1460,7 @@
 
 zx_status_t iwl_mvm_mac_remove_interface(struct iwl_mvm_vif* mvmvif) {
   struct iwl_mvm* mvm = mvmvif->mvm;
+  struct iwl_probe_resp_data* probe_data;
 
 #if 0   // NEEDS_PORTING
     iwl_mvm_prepare_mac_removal(mvm, vif);
@@ -1470,8 +1480,10 @@
 
   mtx_lock(&mvm->mutex);
 
-  free(mvmvif->probe_resp_data);
-  mvmvif->probe_resp_data = NULL;
+  probe_data = iwl_rcu_exchange(mvmvif->probe_resp_data, NULL);
+  if (probe_data) {
+    iwl_rcu_free_sync(mvm->dev, probe_data);
+  }
 
 #if 0  // NEEDS_PORTING
     if (mvm->bf_allowed_vif == mvmvif) {
@@ -1579,12 +1591,10 @@
     return;
   }
 
-  // Only associated client interface can continue. Other interfaces will be ignored.
-  if (mvmvif->mac_role != WLAN_INFO_MAC_ROLE_CLIENT ||
-      mvmvif->mvm->fw_id_to_mac_id[0]->sta_state != IWL_STA_AUTHORIZED) {
-    IWL_ERR(mvmvif, "unexpected state while setting mcast filter. role: %d!=%d or state: %d!=%d\n",
-            mvmvif->mac_role, WLAN_INFO_MAC_ROLE_CLIENT, mvmvif->mvm->fw_id_to_mac_id[0]->sta_state,
-            IWL_STA_AUTHORIZED);
+  // Only client interface can continue. Other interfaces will be ignored.
+  if (mvmvif->mac_role != WLAN_MAC_ROLE_CLIENT || !mvmvif->bss_conf.assoc) {
+    IWL_ERR(mvmvif, "unexpected state while setting mcast filter. role: %d!=%d or assoc: %d!=%d\n",
+            mvmvif->mac_role, WLAN_MAC_ROLE_CLIENT, mvmvif->bss_conf.assoc, true);
     return;
   }
 
@@ -2502,22 +2512,30 @@
 }
 #endif  // NEEDS_PORTING
 
-zx_status_t iwl_mvm_mac_hw_scan(struct iwl_mvm_vif* mvmvif,
-                                const wlan_hw_scan_config_t* scan_config) {
+// Modified from original iwl_mvm_mac_hw_scan() to split call path for active and passive.
+zx_status_t iwl_mvm_mac_hw_scan_passive(struct iwl_mvm_vif* mvmvif,
+                                        const wlan_softmac_passive_scan_args_t* passive_scan_args,
+                                        uint64_t* out_scan_id) {
   struct iwl_mvm* mvm = mvmvif->mvm;
   zx_status_t ret;
 
-  if (scan_config->num_channels == 0 ||
-      scan_config->num_channels > mvm->fw->ucode_capa.n_scan_channels) {
-    IWL_WARN(mvmvif, "Cannot scan: invalid #channel (%d). FW's cap (%d)\n",
-             scan_config->num_channels, mvm->fw->ucode_capa.n_scan_channels);
+  if (passive_scan_args->channels_count == 0 ||
+      passive_scan_args->channels_count > mvm->fw->ucode_capa.n_scan_channels) {
+    IWL_WARN(mvmvif, "Cannot scan: invalid #channel (%zu). FW's cap (%d)\n",
+             passive_scan_args->channels_count, mvm->fw->ucode_capa.n_scan_channels);
     return ZX_ERR_INVALID_ARGS;
   }
 
   mtx_lock(&mvm->mutex);
-  ret = iwl_mvm_reg_scan_start(mvmvif, scan_config);
+  ret = iwl_mvm_reg_scan_start_passive(mvmvif, passive_scan_args);
   mtx_unlock(&mvm->mutex);
 
+  if (ret != ZX_OK) {
+    return ret;
+  }
+
+  // TODO(fxbug.dev/88934): scan_id is always 0
+  *out_scan_id = 0;
   return ret;
 }
 
@@ -2788,7 +2806,7 @@
      * attempts to connect to this AP, and eventually wpa_s will
      * blacklist the AP...
      */
-    if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_CLIENT && mvmvif->bss_conf.beacon_int < 16) {
+    if (mvmvif->mac_role == WLAN_MAC_ROLE_CLIENT && mvmvif->bss_conf.beacon_int < 16) {
       IWL_ERR(mvm, "AP %pM beacon interval is %d, refusing due to firmware bug!\n", mvm_sta->addr,
               mvmvif->bss_conf.beacon_int);
       ret = ZX_ERR_INVALID_ARGS;
@@ -2826,9 +2844,9 @@
     ret = ZX_OK;
 
   } else if (old_state == IWL_STA_AUTH && new_state == IWL_STA_ASSOC) {
-#if 0   // NEEDS_PORTING
-        // TODO(36677): Supports AP role
-        if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_AP) {
+#if 0  // NEEDS_PORTING
+       // TODO(36677): Supports AP role
+        if (mvmvif->mac_role == WLAN_MAC_ROLE_AP) {
             mvmvif->ap_assoc_sta_count++;
             iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
             if (vif->bss_conf.he_support && !iwlwifi_mod_params.disable_11ax) {
@@ -2836,8 +2854,8 @@
             }
         }
 
-        iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, false);
 #endif  // NEEDS_PORTING
+    iwl_mvm_rs_rate_init(mvm, mvm_sta, false);
 
     ret = iwl_mvm_update_sta(mvm, mvm_sta);
 
@@ -2862,7 +2880,7 @@
 
         // TODO(36677): Supports AP role
         /* if wep is used, need to set the key for the station now */
-        if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_AP && mvmvif->ap_wep_key) {
+        if (mvmvif->mac_role == WLAN_MAC_ROLE_AP && mvmvif->ap_wep_key) {
             ret = iwl_mvm_set_sta_key(mvm, vif, sta, mvmvif->ap_wep_key, STA_KEY_IDX_INVALID);
         } else {
             ret = ZX_OK;
@@ -2877,7 +2895,7 @@
   } else if (old_state == IWL_STA_ASSOC && new_state == IWL_STA_AUTH) {
 #if 0   // NEEDS_PORTING
         // TODO(36677): Supports AP role
-        if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_AP) {
+        if (mvmvif->mac_role == WLAN_MAC_ROLE_AP) {
             mvmvif->ap_assoc_sta_count--;
             iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
         }
@@ -2886,17 +2904,6 @@
   } else if (old_state == IWL_STA_AUTH && new_state == IWL_STA_NONE) {
     ret = ZX_OK;
   } else if (old_state == IWL_STA_NONE && new_state == IWL_STA_NOTEXIST) {
-    // Delete all set keys
-    // TODO(fxbug.dev/86728): remove the WPA2 key workaround
-    for (int i = 0; i < STA_KEY_MAX_NUM; i++) {
-      if (mvm->active_key_list[i].keylen) {
-        // delete the key if present
-        if (iwl_mvm_remove_sta_key(mvmvif, mvm_sta, &mvm->active_key_list[i]) != ZX_OK) {
-          IWL_ERR(mvm, "Unable to delete key at offset %d", i);
-        }
-        memset(&mvm->active_key_list[i], 0, sizeof(struct iwl_mvm_sta_key_conf));
-      }
-    }
     ret = iwl_mvm_rm_sta(mvmvif, mvm_sta);
 #if 0   // NEEDS_PORTING
         if (sta->tdls) {
@@ -3041,58 +3048,146 @@
 
     return ret;
 }
+
+static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw,
+			       enum set_key_cmd cmd,
+			       struct ieee80211_vif *vif,
+			       struct ieee80211_sta *sta,
+			       struct ieee80211_key_conf *key)
+{
+	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+	struct iwl_mvm_sta *mvmsta;
+	struct iwl_mvm_key_pn *ptk_pn;
+	int keyidx = key->keyidx;
+	int ret;
+	u8 key_offset;
+
+	if (iwlwifi_mod_params.swcrypto) {
+		IWL_DEBUG_MAC80211(mvm, "leave - hwcrypto disabled\n");
+		return -EOPNOTSUPP;
+	}
+
+	switch (key->cipher) {
+	case WLAN_CIPHER_SUITE_TKIP:
+		if (!mvm->trans->cfg->gen2) {
+			key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
+			key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
+		} else if (vif->type == NL80211_IFTYPE_STATION) {
+			key->flags |= IEEE80211_KEY_FLAG_PUT_MIC_SPACE;
+		} else {
+			IWL_DEBUG_MAC80211(mvm, "Use SW encryption for TKIP\n");
+			return -EOPNOTSUPP;
+		}
+		break;
+	case WLAN_CIPHER_SUITE_CCMP:
+	case WLAN_CIPHER_SUITE_GCMP:
+	case WLAN_CIPHER_SUITE_GCMP_256:
+		if (!iwl_mvm_has_new_tx_api(mvm))
+			key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
+		break;
+	case WLAN_CIPHER_SUITE_AES_CMAC:
+	case WLAN_CIPHER_SUITE_BIP_GMAC_128:
+	case WLAN_CIPHER_SUITE_BIP_GMAC_256:
+		WARN_ON_ONCE(!ieee80211_hw_check(hw, MFP_CAPABLE));
+		break;
+	case WLAN_CIPHER_SUITE_WEP40:
+	case WLAN_CIPHER_SUITE_WEP104:
+		if (vif->type == NL80211_IFTYPE_AP) {
+			struct iwl_mvm_vif *mvmvif =
+				iwl_mvm_vif_from_mac80211(vif);
+
+			mvmvif->ap_wep_key = kmemdup(key,
+						     sizeof(*key) + key->keylen,
+						     GFP_KERNEL);
+			if (!mvmvif->ap_wep_key)
+				return -ENOMEM;
+		}
+
+		if (vif->type != NL80211_IFTYPE_STATION)
+			return 0;
+		break;
+	default:
+		/* currently FW supports only one optional cipher scheme */
+		if (hw->n_cipher_schemes &&
+		    hw->cipher_schemes->cipher == key->cipher)
+			key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
+		else
+			return -EOPNOTSUPP;
+	}
+
+	switch (cmd) {
+	case SET_KEY:
+		ret = iwl_mvm_mac_add_key(vif, sta, key);
+	case DISABLE_KEY:
+		ret = iwl_mvm_mac_remove_key(vif, sta, key);
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
 #endif  // NEEDS_PORTING
 
-zx_status_t iwl_mvm_mac_set_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
-                                const struct iwl_mvm_sta_key_conf* key) {
+zx_status_t iwl_mvm_mac_add_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
+                                struct ieee80211_key_conf* key) {
   zx_status_t ret = ZX_OK;
   struct iwl_mvm* mvm = mvmvif->mvm;
-  struct iwl_mvm_key_pn* ptk_pn;
+  struct iwl_mvm_key_pn* ptk_pn = NULL;
   uint8_t key_offset = 0;
 
-  if (iwlwifi_mod_params.swcrypto) {
-    IWL_DEBUG_MAC80211(mvm, "leave - hwcrypto disabled\n");
-    return ZX_ERR_NOT_SUPPORTED;
-  }
-
-  switch (key->cipher_type) {
+  // Fuchsia only supports a limited selection of cipher types for now.
+  switch (key->cipher) {
     case CIPHER_SUITE_TYPE_CCMP_128:
+      // Note: the Linux iwlwifi driver requests IEEE80211_KEY_FLAG_PUT_IV_SPACE from the mac80211
+      // stack.  We will apply equivalent functionality manually to Incoming packets from Fuchsia.
       if (iwl_mvm_has_new_tx_api(mvm)) {
         return ZX_ERR_NOT_SUPPORTED;
       }
       break;
+    case CIPHER_SUITE_TYPE_BIP_CMAC_128:
+      break;
     default:
       return ZX_ERR_NOT_SUPPORTED;
   }
 
   mtx_lock(&mvm->mutex);
 
-  // Porting note: the following is the equivalent of just the SET_KEY path, as Fuchsia does not
-  // have the equivalent to a DELETE_KEY call.
-
-  if ((mvmvif->mac_role == WLAN_INFO_MAC_ROLE_MESH || mvmvif->mac_role == WLAN_INFO_MAC_ROLE_AP) &&
-      !mvmsta) {
+  if ((mvmvif->mac_role == WLAN_MAC_ROLE_MESH || mvmvif->mac_role == WLAN_MAC_ROLE_AP) && !mvmsta) {
     /*
      * GTK on AP interface is a TX-only key, return 0;
      * on IBSS they're per-station and because we're lazy
      * we don't support them for RX, so do the same.
      * CMAC/GMAC in AP/IBSS modes must be done in software.
      */
-    if (key->cipher_type == CIPHER_SUITE_TYPE_BIP_CMAC_128 ||
-        key->cipher_type == CIPHER_SUITE_TYPE_BIP_GMAC_128 ||
-        key->cipher_type == CIPHER_SUITE_TYPE_BIP_GMAC_256) {
+    if (key->cipher == CIPHER_SUITE_TYPE_BIP_CMAC_128 ||
+        key->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_128 ||
+        key->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_256) {
       ret = ZX_ERR_NOT_SUPPORTED;
     } else {
       ret = ZX_OK;
     }
+
+    if (key->cipher != CIPHER_SUITE_TYPE_GCMP_128 && key->cipher != CIPHER_SUITE_TYPE_GCMP_256 &&
+        !iwl_mvm_has_new_tx_api(mvm)) {
+      key->hw_key_idx = STA_KEY_IDX_INVALID;
+      goto out;
+    }
+  }
+
+  /* During FW restart, in order to restore the state as it was,
+   * don't try to reprogram keys we previously failed for.
+   */
+  if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) &&
+      key->hw_key_idx == STA_KEY_IDX_INVALID) {
+    IWL_DEBUG_MAC80211(mvm, "skip invalid idx key programming during restart\n");
+    ret = ZX_OK;
+    goto out;
   }
 
   if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && mvmsta &&
       iwl_mvm_has_new_rx_api(mvm) && key->key_type == WLAN_KEY_TYPE_PAIRWISE &&
-      (key->cipher_type == CIPHER_SUITE_TYPE_CCMP_128 ||
-
-       key->cipher_type == CIPHER_SUITE_TYPE_GCMP_128 ||
-       key->cipher_type == CIPHER_SUITE_TYPE_GCMP_256)) {
+      (key->cipher == CIPHER_SUITE_TYPE_CCMP_128 || key->cipher == CIPHER_SUITE_TYPE_GCMP_128 ||
+       key->cipher == CIPHER_SUITE_TYPE_GCMP_256)) {
     int tid, q;
 
     ptk_pn = calloc(1, sizeof(*ptk_pn) + sizeof(ptk_pn->q->pn) * mvm->trans->num_rx_queues);
@@ -3103,19 +3198,15 @@
 
     for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
       for (q = 0; q < mvm->trans->num_rx_queues; q++) {
-        memset(ptk_pn->q[q].pn[tid], 0, IEEE80211_CCMP_PN_LEN);
+        /* The packet number in packet byte order is little-endian */
+        uint64_t pn_le = cpu_to_le64(key->rx_seq);
+        memcpy(ptk_pn->q[q].pn[tid], &pn_le, IEEE80211_CCMP_PN_LEN);
       }
     }
 
-    struct iwl_mvm_key_pn* old_ptk_pn = NULL;
-
-    mtx_lock(&mvmsta->ptk_pn_mutex);
-    old_ptk_pn = mvmsta->ptk_pn[key->keyidx];
-    mvmsta->ptk_pn[key->keyidx] = ptk_pn;
-    mtx_unlock(&mvmsta->ptk_pn_mutex);
-
+    struct iwl_mvm_key_pn* old_ptk_pn = iwl_rcu_exchange(mvmsta->ptk_pn[key->keyidx], ptk_pn);
     if (old_ptk_pn) {
-      free(old_ptk_pn);
+      iwl_rcu_free_sync(mvm->dev, old_ptk_pn);
     }
   }
 
@@ -3143,6 +3234,35 @@
   return ret;
 }
 
+zx_status_t iwl_mvm_mac_remove_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
+                                   const struct ieee80211_key_conf* key) {
+  zx_status_t ret = ZX_OK;
+  struct iwl_mvm* mvm = mvmvif->mvm;
+  struct iwl_mvm_key_pn* ptk_pn = NULL;
+
+  mtx_lock(&mvm->mutex);
+
+  if (key->hw_key_idx == STA_KEY_IDX_INVALID) {
+    ret = 0;
+    goto out;
+  }
+
+  if (mvmsta && iwl_mvm_has_new_rx_api(mvm) && key->key_type == WLAN_KEY_TYPE_PAIRWISE &&
+      (key->cipher == CIPHER_SUITE_TYPE_CCMP_128 || key->cipher == CIPHER_SUITE_TYPE_GCMP_128 ||
+       key->cipher == CIPHER_SUITE_TYPE_GCMP_256)) {
+    ptk_pn = iwl_rcu_exchange(mvmsta->ptk_pn[key->keyidx], NULL);
+    if (ptk_pn)
+      iwl_rcu_free_sync(mvm->dev, ptk_pn);
+  }
+
+  IWL_DEBUG_MAC80211(mvm, "disable hwcrypto key\n");
+  ret = iwl_mvm_remove_sta_key(mvm, mvmvif, mvmsta, key);
+
+out:
+  mtx_unlock(&mvm->mutex);
+  return ret;
+}
+
 #if 0  // NEEDS_PORTING
 static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw* hw, struct ieee80211_vif* vif,
                                         struct ieee80211_key_conf* keyconf,
@@ -3553,7 +3673,7 @@
         ret = 0;
         goto out;
 #endif  // NEEDS_PORTING
-    case WLAN_INFO_MAC_ROLE_CLIENT:
+    case WLAN_MAC_ROLE_CLIENT:
       mvmvif->csa_bcn_pending = false;
       break;
 #if 0   // NEEDS_PORTING
@@ -3605,7 +3725,7 @@
     }
 #endif  // NEEDS_PORTING
 
-  if (switching_chanctx && mvmvif->mac_role == WLAN_INFO_MAC_ROLE_CLIENT) {
+  if (switching_chanctx && mvmvif->mac_role == WLAN_MAC_ROLE_CLIENT) {
     uint32_t duration = 3 * mvmvif->bss_conf.beacon_int;
 
     /* iwl_mvm_protect_session() reads directly from the
@@ -3687,7 +3807,7 @@
         mvmvif->ap_ibss_active = false;
         break;
 #endif  // NEEDS_PORTING
-    case WLAN_INFO_MAC_ROLE_CLIENT:
+    case WLAN_MAC_ROLE_CLIENT:
       if (!switching_chanctx) {
         break;
       }
diff --git a/third_party/iwlwifi/mvm/mvm.h b/third_party/iwlwifi/mvm/mvm.h
index 21d34dd..5d7f71f 100644
--- a/third_party/iwlwifi/mvm/mvm.h
+++ b/third_party/iwlwifi/mvm/mvm.h
@@ -37,13 +37,11 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_MVM_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_MVM_H_
 
+#include <fuchsia/hardware/wlanphyimpl/c/banjo.h>
 #include <threads.h>
 #include <zircon/listnode.h>
 #include <zircon/time.h>
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
-//#include <wlan/protocol/mac.h>
-
 #include "third_party/iwlwifi/fw/acpi.h"
 #include "third_party/iwlwifi/fw/dbg.h"
 #include "third_party/iwlwifi/fw/file.h"
@@ -139,6 +137,7 @@
   uint32_t ref;
 
   wlan_channel_t chandef;
+  wlan_info_band_t band;
 
 #ifdef CPTCFG_IWLWIFI_FRQ_MGR
   /* Frequency Manager tx power limit*/
@@ -473,9 +472,9 @@
 
   /* Zircon objects */
   struct zx_device* zxdev;
-  wlan_info_mac_role_t mac_role;
+  wlan_mac_role_t mac_role;
   zx_handle_t mlme_channel;  // Channel passed from devmgr. Will be passed to MLME at mac_start().
-  wlanmac_ifc_protocol_t ifc;
+  wlan_softmac_ifc_protocol_t ifc;
 
   // Merged from 'struct ieee80211_vif'
   bool ht_enabled;
@@ -1026,8 +1025,6 @@
    */
   unsigned long fw_key_table[BITS_TO_LONGS(STA_KEY_MAX_NUM)];
   uint8_t fw_key_deleted[STA_KEY_MAX_NUM];
-  // TODO(fxbug.dev/86728): remove the WPA2 key workaround
-  struct iwl_mvm_sta_key_conf active_key_list[STA_KEY_MAX_NUM];
 
   /* references taken by the driver and spinlock protecting them */
   mtx_t refs_lock;
@@ -1587,7 +1584,8 @@
 int iwl_mvm_tx_skb_non_sta(struct iwl_mvm* mvm, struct sk_buff* skb);
 void iwl_mvm_set_tx_cmd(struct iwl_mvm* mvm, struct ieee80211_mac_packet* pkt,
                         struct iwl_tx_cmd* tx_cmd, uint8_t sta_id);
-void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm* mvm, struct iwl_tx_cmd* tx_cmd);
+void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm* mvm, struct iwl_tx_cmd* tx_cmd,
+                             const struct ieee80211_frame_header* hdr);
 void iwl_mvm_mac_itxq_xmit(struct ieee80211_hw* hw, struct ieee80211_txq* txq);
 unsigned int iwl_mvm_max_amsdu_size(struct iwl_mvm* mvm, struct ieee80211_sta* sta,
                                     unsigned int tid);
@@ -1604,7 +1602,7 @@
 
 void iwl_mvm_async_handlers_purge(struct iwl_mvm* mvm);
 
-static inline void iwl_mvm_set_tx_cmd_ccmp(struct iwl_mvm_sta_key_conf* keyconf,
+static inline void iwl_mvm_set_tx_cmd_ccmp(struct ieee80211_key_conf* keyconf,
                                            struct iwl_tx_cmd* tx_cmd) {
   tx_cmd->sec_ctl = TX_CMD_SEC_CCM;
   memcpy(tx_cmd->key, keyconf->key, keyconf->keylen);
@@ -1767,8 +1765,10 @@
 #endif
 
 /* Scanning */
-zx_status_t iwl_mvm_reg_scan_start(struct iwl_mvm_vif* mvmvif,
-                                   const wlan_hw_scan_config_t* scan_config);
+zx_status_t iwl_mvm_reg_scan_start_passive(
+    struct iwl_mvm_vif* mvmvif, const wlan_softmac_passive_scan_args_t* passive_scan_args);
+zx_status_t iwl_mvm_reg_scan_start(struct iwl_mvm_vif* mvmvif, const uint8_t* channel_list_buffer,
+                                   size_t channel_list_size);
 int iwl_mvm_scan_size(struct iwl_mvm* mvm);
 int iwl_mvm_scan_stop(struct iwl_mvm* mvm, int type, bool notify);
 int iwl_mvm_max_scan_ie_len(struct iwl_mvm* mvm);
@@ -1946,7 +1946,7 @@
  * command queue, which can't be flushed.
  */
 static inline uint32_t iwl_mvm_flushable_queues(struct iwl_mvm* mvm) {
-  return ((BIT(mvm->cfg->base_params->num_of_queues) - 1) & ~BIT(IWL_MVM_DQA_CMD_QUEUE));
+  return ((uint32_t)(BIT(mvm->cfg->base_params->num_of_queues) - 1) & ~BIT(IWL_MVM_DQA_CMD_QUEUE));
 }
 
 static inline void iwl_mvm_stop_device(struct iwl_mvm* mvm) {
@@ -2005,14 +2005,27 @@
 int iwl_mvm_fm_unregister(struct iwl_mvm* mvm);
 #endif
 
-/* Location Aware Regulatory */
-struct iwl_mcc_update_resp* iwl_mvm_update_mcc(struct iwl_mvm* mvm, const char* alpha2,
-                                               enum iwl_mcc_source src_id);
-int iwl_mvm_init_mcc(struct iwl_mvm* mvm);
+//------------------------------------------------------------------------------------------------
+// Location Aware Regulatory
+
+// Send the 2-byte country code to the firmware.
+//
+// When this function returns ZX_OK, the 'out_resp_cp' will be pointed to a
+// 'struct iwl_mcc_update_resp' object (variable-sized).  It is the caller's responsibility
+// to release the memory.
+zx_status_t iwl_mvm_update_mcc(struct iwl_mvm* mvm, const char* alpha2, enum iwl_mcc_source src_id,
+                               struct iwl_mcc_update_resp** out_resp_cp);
+
+zx_status_t iwl_mvm_init_mcc(struct iwl_mvm* mvm);
 void iwl_mvm_rx_chub_update_mcc(struct iwl_mvm* mvm, struct iwl_rx_cmd_buffer* rxb);
-struct ieee80211_regdomain* iwl_mvm_get_regdomain(struct wiphy* wiphy, const char* alpha2,
-                                                  enum iwl_mcc_source src_id, bool* changed);
-struct ieee80211_regdomain* iwl_mvm_get_current_regdomain(struct iwl_mvm* mvm, bool* changed);
+
+zx_status_t iwl_mvm_get_regdomain(struct iwl_mvm* mvm, const char* alpha2,
+                                  enum iwl_mcc_source src_id, bool* changed,
+                                  wlanphy_country_t* out_country);
+
+zx_status_t iwl_mvm_get_current_regdomain(struct iwl_mvm* mvm, bool* changed,
+                                          wlanphy_country_t* out_country);
+
 int iwl_mvm_init_fw_regd(struct iwl_mvm* mvm);
 void iwl_mvm_update_changed_regdom(struct iwl_mvm* mvm);
 
@@ -2134,7 +2147,8 @@
 //
 // Interfaces for mac80211.c
 //
-zx_status_t iwl_mvm_mac_tx(struct iwl_mvm_vif* mvmvif, struct ieee80211_mac_packet* pkt);
+zx_status_t iwl_mvm_mac_tx(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
+                           struct ieee80211_mac_packet* pkt);
 
 zx_status_t iwl_mvm_find_free_mvmvif_slot(struct iwl_mvm* mvm, int* ret_idx);
 zx_status_t iwl_mvm_bind_mvmvif(struct iwl_mvm* mvm, int idx, struct iwl_mvm_vif* mvmvif);
@@ -2144,8 +2158,9 @@
 
 void iwl_mvm_configure_filter(struct iwl_mvm* mvm);
 
-zx_status_t iwl_mvm_mac_hw_scan(struct iwl_mvm_vif* mvmvif,
-                                const wlan_hw_scan_config_t* scan_config);
+zx_status_t iwl_mvm_mac_hw_scan_passive(struct iwl_mvm_vif* mvmvif,
+                                        const wlan_softmac_passive_scan_args_t* passive_scan_args,
+                                        uint64_t* out_scan_id);
 
 zx_status_t iwl_mvm_mac_sta_state(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta,
                                   enum iwl_sta_state old_state, enum iwl_sta_state new_state);
@@ -2153,8 +2168,10 @@
 void iwl_mvm_mac_mgd_prepare_tx(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvmvif,
                                 uint16_t req_duration);
 
-zx_status_t iwl_mvm_mac_set_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
-                                const struct iwl_mvm_sta_key_conf* key);
+zx_status_t iwl_mvm_mac_add_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
+                                struct ieee80211_key_conf* key);
+zx_status_t iwl_mvm_mac_remove_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
+                                   const struct ieee80211_key_conf* key);
 
 zx_status_t iwl_mvm_add_chanctx(struct iwl_mvm* mvm, const wlan_channel_t* channeldef,
                                 uint16_t* phy_ctxt_id);
diff --git a/third_party/iwlwifi/mvm/nvm.c b/third_party/iwlwifi/mvm/nvm.c
index 76979e3..e40aafa 100644
--- a/third_party/iwlwifi/mvm/nvm.c
+++ b/third_party/iwlwifi/mvm/nvm.c
@@ -34,6 +34,8 @@
  *
  *****************************************************************************/
 
+#include <zircon/status.h>
+
 #include "third_party/iwlwifi/fw/acpi.h"
 #include "third_party/iwlwifi/iwl-csr.h"
 #include "third_party/iwlwifi/iwl-eeprom-parse.h"
@@ -42,6 +44,7 @@
 #include "third_party/iwlwifi/iwl-prph.h"
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
+#include "third_party/iwlwifi/platform/compiler.h"
 
 #if 0   // NEEDS_PORTING
 /*
@@ -430,14 +433,13 @@
   return ret;
 }
 
-#if 0   // NEEDS_PORTING
-struct iwl_mcc_update_resp* iwl_mvm_update_mcc(struct iwl_mvm* mvm, const char* alpha2,
-                                               enum iwl_mcc_source src_id) {
+zx_status_t iwl_mvm_update_mcc(struct iwl_mvm* mvm, const char* alpha2, enum iwl_mcc_source src_id,
+                               struct iwl_mcc_update_resp** out_resp_cp) {
   struct iwl_mcc_update_cmd mcc_update_cmd = {
       .mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]),
       .source_id = (uint8_t)src_id,
   };
-  struct iwl_mcc_update_resp* resp_cp;
+  struct iwl_mcc_update_resp* resp_cp = NULL;
   struct iwl_rx_packet* pkt;
   struct iwl_host_cmd cmd = {
       .id = MCC_UPDATE_CMD,
@@ -445,22 +447,26 @@
       .data = {&mcc_update_cmd},
   };
 
-  int ret;
+  zx_status_t ret;
   uint32_t status;
   int resp_len, n_channels;
   uint16_t mcc;
 
-  if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) {
-    return ERR_PTR(-EOPNOTSUPP);
+  if (!iwl_mvm_is_lar_supported(mvm)) {
+    IWL_WARN(mvm, "LAR is not supported. Ignore update MCC.\n");
+    return ZX_ERR_NOT_SUPPORTED;
   }
 
+  ZX_ASSERT(out_resp_cp);
+
   cmd.len[0] = sizeof(struct iwl_mcc_update_cmd);
 
   IWL_DEBUG_LAR(mvm, "send MCC update to FW with '%c%c' src = %d\n", alpha2[0], alpha2[1], src_id);
 
   ret = iwl_mvm_send_cmd(mvm, &cmd);
-  if (ret) {
-    return ERR_PTR(ret);
+  if (ret != ZX_OK) {
+    IWL_ERR(mvm, "MCC update command failed: %s\n", zx_status_get_string(ret));
+    return ret;
   }
 
   pkt = cmd.resp_pkt;
@@ -469,21 +475,22 @@
   if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT)) {
     struct iwl_mcc_update_resp* mcc_resp = (void*)pkt->data;
 
-    n_channels = __le32_to_cpu(mcc_resp->n_channels);
+    n_channels = le32_to_cpu(mcc_resp->n_channels);
     resp_len = sizeof(struct iwl_mcc_update_resp) + n_channels * sizeof(__le32);
-    resp_cp = kmemdup(mcc_resp, resp_len, GFP_KERNEL);
+    resp_cp = calloc(1, resp_len);
     if (!resp_cp) {
-      resp_cp = ERR_PTR(-ENOMEM);
+      ret = ZX_ERR_NO_MEMORY;
       goto exit;
     }
+    memcpy(resp_cp, mcc_resp, resp_len);
   } else {
     struct iwl_mcc_update_resp_v3* mcc_resp_v3 = (void*)pkt->data;
 
-    n_channels = __le32_to_cpu(mcc_resp_v3->n_channels);
+    n_channels = le32_to_cpu(mcc_resp_v3->n_channels);
     resp_len = sizeof(struct iwl_mcc_update_resp) + n_channels * sizeof(__le32);
-    resp_cp = kzalloc(resp_len, GFP_KERNEL);
+    resp_cp = calloc(1, resp_len);
     if (!resp_cp) {
-      resp_cp = ERR_PTR(-ENOMEM);
+      ret = ZX_ERR_NO_MEMORY;
       goto exit;
     }
 
@@ -507,19 +514,21 @@
     resp_cp->mcc = cpu_to_le16(mcc);
   }
 
+  *out_resp_cp = resp_cp;
+
   IWL_DEBUG_LAR(mvm, "MCC response status: 0x%x. new MCC: 0x%x ('%c%c') n_chans: %d\n", status, mcc,
                 mcc >> 8, mcc & 0xff, n_channels);
 
 exit:
   iwl_free_resp(&cmd);
-  return resp_cp;
+  return ret;
 }
 
-int iwl_mvm_init_mcc(struct iwl_mvm* mvm) {
+zx_status_t iwl_mvm_init_mcc(struct iwl_mvm* mvm) {
   bool tlv_lar;
   bool nvm_lar;
-  int retval;
-  struct ieee80211_regdomain* regd;
+  zx_status_t retval;
+  wlanphy_country_t country;
   char mcc[3];
 
   if (mvm->cfg->nvm_type == IWL_NVM_EXT) {
@@ -531,17 +540,19 @@
   }
 
   if (!iwl_mvm_is_lar_supported(mvm)) {
-    return 0;
+    return ZX_OK;
   }
 
+#if 0   // NEEDS_PORTING
   /*
    * try to replay the last set MCC to FW. If it doesn't exist,
    * queue an update to cfg80211 to retrieve the default alpha2 from FW.
    */
   retval = iwl_mvm_init_fw_regd(mvm);
-  if (retval != -ENOENT) {
+  if (retval != ZX_ERR_NO_RESOURCES) {
     return retval;
   }
+#endif  // NEEDS_PORTING
 
   /*
    * Driver regulatory hint for initial update, this also informs the
@@ -551,24 +562,27 @@
    */
   mvm->lar_regdom_set = false;
 
-  regd = iwl_mvm_get_current_regdomain(mvm, NULL);
-  if (IS_ERR_OR_NULL(regd)) {
-    return -EIO;
+  retval = iwl_mvm_get_current_regdomain(mvm, NULL, &country);
+  if (retval != ZX_OK) {
+    return ZX_ERR_BAD_STATE;
   }
 
   if (iwl_mvm_is_wifi_mcc_supported(mvm) && !iwl_acpi_get_mcc(mvm->dev, mcc)) {
-    kfree(regd);
-    regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, MCC_SOURCE_BIOS, NULL);
-    if (IS_ERR_OR_NULL(regd)) {
-      return -EIO;
+    retval = iwl_mvm_get_regdomain(mvm, mcc, MCC_SOURCE_BIOS, NULL, &country);
+    if (retval != ZX_OK) {
+      return ZX_ERR_BAD_STATE;
     }
   }
 
+#if 0   // NEEDS_PORTING
   retval = regulatory_set_wiphy_regd_sync_rtnl(mvm->hw->wiphy, regd);
   kfree(regd);
+#endif  // NEEDS_PORTING
+
   return retval;
 }
 
+#if 0   // NEEDS_PORTING
 void iwl_mvm_rx_chub_update_mcc(struct iwl_mvm* mvm, struct iwl_rx_cmd_buffer* rxb) {
   struct iwl_rx_packet* pkt = rxb_addr(rxb);
   struct iwl_mcc_chub_notif* notif = (void*)pkt->data;
diff --git a/third_party/iwlwifi/mvm/ops.c b/third_party/iwlwifi/mvm/ops.c
index ff6843c..a14b2a3 100644
--- a/third_party/iwlwifi/mvm/ops.c
+++ b/third_party/iwlwifi/mvm/ops.c
@@ -36,8 +36,8 @@
 
 #include <stdbool.h>
 #include <threads.h>
-#include <zircon/syscalls.h>
 #include <zircon/compiler.h>
+#include <zircon/syscalls.h>
 
 #include <ddk/hw/wlan/ieee80211/c/banjo.h>
 
@@ -63,6 +63,7 @@
 #include "third_party/iwlwifi/mvm/mvm.h"
 #include "third_party/iwlwifi/mvm/rs.h"
 #include "third_party/iwlwifi/mvm/time-event.h"
+#include "third_party/iwlwifi/platform/rcu.h"
 
 #ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE
 #include "third_party/iwlwifi/iwl-dnt-cfg.h"
@@ -260,8 +261,11 @@
     RX_HANDLER(STATISTICS_NOTIFICATION, iwl_mvm_rx_statistics, RX_HANDLER_ASYNC_LOCKED),
 
     RX_HANDLER(BA_WINDOW_STATUS_NOTIFICATION_ID, iwl_mvm_window_status_notif, RX_HANDLER_SYNC),
+#endif  // NEEDS_PORTING
 
     RX_HANDLER(TIME_EVENT_NOTIFICATION, iwl_mvm_rx_time_event_notif, RX_HANDLER_SYNC),
+
+#if 0   // NEEDS_PORTING
     RX_HANDLER(MCC_CHUB_UPDATE_CMD, iwl_mvm_rx_chub_update_mcc, RX_HANDLER_ASYNC_LOCKED),
 
     RX_HANDLER(EOSP_NOTIFICATION, iwl_mvm_rx_eosp_notif, RX_HANDLER_SYNC),
@@ -606,8 +610,7 @@
 }
 #endif
 
-// TODO(rsakthi) - Reintroduce TA_NO_THREAD_SAFETY_ANALYSIS
-static int iwl_mvm_fwrt_dump_start(void* ctx) { //TA_NO_THREAD_SAFETY_ANALYSIS {
+static int iwl_mvm_fwrt_dump_start(void* ctx) __TA_NO_THREAD_SAFETY_ANALYSIS {
   struct iwl_mvm* mvm = ctx;
   int ret;
 
@@ -621,7 +624,7 @@
   return 0;
 }
 
-static void iwl_mvm_fwrt_dump_end(void* ctx) { //TA_NO_THREAD_SAFETY_ANALYSIS {
+static void iwl_mvm_fwrt_dump_end(void* ctx) __TA_NO_THREAD_SAFETY_ANALYSIS {
   struct iwl_mvm* mvm = ctx;
 
   mtx_unlock(&mvm->mutex);
@@ -1304,9 +1307,9 @@
     return;
   }
 
-  rcu_read_lock();
+  iwl_rcu_read_lock(mvm->dev);
 
-  mvmsta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]);
+  mvmsta = iwl_rcu_load(mvm->fw_id_to_mac_id[sta_id]);
   if (IS_ERR_OR_NULL(mvmsta)) {
     goto out;
   }
@@ -1339,7 +1342,7 @@
   }
 
 out:
-  rcu_read_unlock();
+  iwl_rcu_read_unlock(mvm->dev);
 }
 
 static void iwl_mvm_stop_sw_queue(struct iwl_op_mode* op_mode, int hw_queue) {
@@ -1624,9 +1627,9 @@
     return;
   }
 
-  rcu_read_lock();
+  iwl_rcu_read_lock(mvm->dev);
 
-  ap_sta = rcu_dereference(mvm->fw_id_to_mac_id[iter_data->ap_sta_id]);
+  ap_sta = iwl_rcu_load(mvm->fw_id_to_mac_id[iter_data->ap_sta_id]);
   if (IS_ERR_OR_NULL(ap_sta)) {
     goto out;
   }
@@ -1642,7 +1645,7 @@
    */
   iwl_mvm_set_wowlan_qos_seq(mvm_ap_sta, cmd);
 out:
-  rcu_read_unlock();
+  iwl_rcu_read_unlock(mvm->dev);
 }
 
 int iwl_mvm_enter_d0i3(struct iwl_op_mode* op_mode) {
diff --git a/third_party/iwlwifi/mvm/phy-ctxt.c b/third_party/iwlwifi/mvm/phy-ctxt.c
index 72e1b1d..581d82e 100644
--- a/third_party/iwlwifi/mvm/phy-ctxt.c
+++ b/third_party/iwlwifi/mvm/phy-ctxt.c
@@ -33,9 +33,8 @@
  *
  *****************************************************************************/
 
-#include <fuchsia/hardware/wlan/info/c/banjo.h>
-
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/associnfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
 
 #include "third_party/iwlwifi/mvm/fw-api.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
@@ -56,7 +55,7 @@
 // Returns:
 //   the band ID.
 wlan_info_band_t iwl_mvm_get_channel_band(uint8_t chan_num) {
-  return chan_num < 14 ? WLAN_INFO_BAND_2GHZ : WLAN_INFO_BAND_5GHZ;
+  return chan_num < 14 ? WLAN_INFO_BAND_TWO_GHZ : WLAN_INFO_BAND_FIVE_GHZ;
 }
 
 /* Maps the driver specific channel width definition to the fw values */
@@ -226,8 +225,8 @@
   uint8_t active_cnt, idle_cnt;
 
   /* Set the channel info data */
-  cmd->ci.band =
-      iwl_mvm_get_channel_band(chandef->primary) == WLAN_INFO_BAND_2GHZ ? PHY_BAND_24 : PHY_BAND_5;
+  cmd->ci.band = iwl_mvm_get_channel_band(chandef->primary) == WLAN_INFO_BAND_TWO_GHZ ? PHY_BAND_24
+                                                                                      : PHY_BAND_5;
 
   cmd->ci.channel = chandef->primary;
   cmd->ci.width = iwl_mvm_get_channel_width(chandef);
diff --git a/third_party/iwlwifi/mvm/power.c b/third_party/iwlwifi/mvm/power.c
index 7f2bc7d..8be67d8 100644
--- a/third_party/iwlwifi/mvm/power.c
+++ b/third_party/iwlwifi/mvm/power.c
@@ -53,7 +53,7 @@
 
 #include <zircon/status.h>
 
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 #include "third_party/iwlwifi/iwl-debug.h"
 #include "third_party/iwlwifi/iwl-modparams.h"
@@ -576,7 +576,7 @@
       break;
 #endif  // NEEDS_PORTING
 
-    case WLAN_INFO_MAC_ROLE_CLIENT:
+    case WLAN_MAC_ROLE_CLIENT:
       power_iterator->bss_vif = mvmvif;
       if (active) {
         power_iterator->bss_active = true;
@@ -776,7 +776,7 @@
   struct iwl_mvm* mvm = mvmvif->mvm;
 
   if (mvmvif != mvm->bf_allowed_vif || !mvmvif->bss_conf.dtim_period ||
-      mvmvif->mac_role != WLAN_INFO_MAC_ROLE_CLIENT || mvmvif->p2p) {
+      mvmvif->mac_role != WLAN_MAC_ROLE_CLIENT || mvmvif->p2p) {
     return ZX_OK;
   }
 
@@ -809,7 +809,7 @@
                                                   bool d0i3) {
   struct iwl_beacon_filter_cmd cmd = {};
 
-  if (mvmvif->mac_role != WLAN_INFO_MAC_ROLE_CLIENT || mvmvif->p2p) {
+  if (mvmvif->mac_role != WLAN_MAC_ROLE_CLIENT || mvmvif->p2p) {
     return ZX_OK;
   }
 
diff --git a/third_party/iwlwifi/mvm/rateScaleMng.c b/third_party/iwlwifi/mvm/rateScaleMng.c
index 19e7ed9..9485dd6 100644
--- a/third_party/iwlwifi/mvm/rateScaleMng.c
+++ b/third_party/iwlwifi/mvm/rateScaleMng.c
@@ -33,9 +33,11 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *****************************************************************************/
-#include "_rateScaleMng.h"
 
-#define BOOLEAN bool
+#include <zircon/syscalls.h>
+
+#include "_rateScaleMng.h"
+#include "mvm.h"
 
 #define SHIFT_AND_MASK(val, mask, pos) (((val) >> (pos)) & ((mask) >> (pos)))
 #define SEC_TO_USEC(x) ((x)*USEC_PER_SEC)
@@ -49,9 +51,58 @@
 #define MSB2ORD msb2ord
 #define LSB2ORD lsb2ord
 
-static inline unsigned long msb2ord(unsigned long x) { return find_last_bit(&x, BITS_PER_LONG); }
+static inline unsigned long msb2ord(unsigned long x) {
+  return find_last_bit((unsigned int*)&x, BITS_PER_LONG);
+}
 
-static inline unsigned long lsb2ord(unsigned long x) { return find_first_bit(&x, BITS_PER_LONG); }
+static inline unsigned long lsb2ord(unsigned long x) {
+  return find_first_bit((unsigned int*)&x, BITS_PER_LONG);
+}
+
+uint64_t nonht_rate_to_bit(uint8_t rate_value) {
+  uint64_t rate_bit = 0;
+  switch (rate_value) {
+    case 1:
+      rate_bit = BIT(RS_NON_HT_RATE_CCK_1M);
+      break;
+    case 2:
+      rate_bit = BIT(RS_NON_HT_RATE_CCK_2M);
+      break;
+    case 11:
+      rate_bit = BIT(RS_NON_HT_RATE_CCK_5_5M);
+      break;
+    case 22:
+      rate_bit = BIT(RS_NON_HT_RATE_CCK_11M);
+      break;
+    case 6:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_6M);
+      break;
+    case 9:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_9M);
+      break;
+    case 12:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_12M);
+      break;
+    case 18:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_18M);
+      break;
+    case 24:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_24M);
+      break;
+    case 36:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_36M);
+      break;
+    case 48:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_48M);
+      break;
+    case 54:
+      rate_bit = BIT(RS_NON_HT_RATE_OFDM_54M);
+      break;
+    default:
+      IWL_WARN(NULL, "Not non-HT rate: %u", rate_value);
+  }
+  return rate_bit;
+}
 
 // TODO - move to coex.c
 static bool btCoexManagerIsAntAvailable(struct iwl_mvm* mvm, uint8_t ant) {
@@ -97,6 +148,8 @@
     [RS_NON_HT_RATE_OFDM_48M] = R_48M,  [RS_NON_HT_RATE_OFDM_54M] = R_54M,
 };
 
+#if 0   // NEEDS_PORTING
+// TODO(fxbug.dev/36684): Supports VHT (802.11ac)
 // This array converts VHT rate configuration to phy rate
 // See ieee80211-2016 spec, section 21.4 tabled for reference values
 // Note the values are in bps, instead of mbps (i.e. multiplied by 2^20)
@@ -111,6 +164,7 @@
     [CHANNEL_WIDTH160] = {61341696, 122683392, 184025088, 245366784, 368050176, 490733568,
                           552075264, 613416960, 736100352, 817889280},
 };
+#endif  // NEEDS_PORTING
 
 // The array cell index is the MCS. e.g. - cell 0 - MCS0 6M. etc.
 static const U08 downColMcsToLegacy[] = {
@@ -144,6 +198,7 @@
     RS_MNG_DYN_BW_STAY_MCS(20, 0, 1, 0, 1), RS_MNG_DYN_BW_STAY_MCS(40, 2, 2, 2, 2),
     RS_MNG_DYN_BW_STAY_MCS(80, 5, 8, 4, 7), RS_MNG_DYN_BW_STAY_MCS(160, 7, 9, 6, 9)};
 
+#if 0   // NEEDS_PORTING
 /***********************************************************************/
 /*
  * The following tables contain the expected throughput metrics for all rates
@@ -690,6 +745,7 @@
         },
     },
 };
+#endif  // NEEDS_PORTING
 
 /*******************************************************************************/
 
@@ -700,6 +756,7 @@
 // info about the relevant fields can be found for LINK_QUAL_AGG_PARAMS_API_S
 #define RS_MNG_AGG_DISABLE_START_TH 3
 
+#if 0   // TODO(fxbug.dev/36684): Supports VHT (802.11ac)
 /*******************************************************************************/
 static const RS_MNG_STA_LIMITS_S g_rsMngStaModLimits[] = {
     {
@@ -716,21 +773,21 @@
         .statsFlushTimeLimit = RS_MNG_STATS_FLUSH_TIME_LIMIT,
         .clearTblWindowsLimit = RS_MNG_LEGACY_MOD_COUNTER_LIMIT,
     }};
+#endif  // NEEDS_PORTING
 
-static BOOLEAN _allowColAnt(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                            const RS_MNG_COL_ELEM_S* nextCol);
-static BOOLEAN _allowColMimo(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                             const RS_MNG_COL_ELEM_S* nextCol);
-static BOOLEAN _allowColSiso(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                             const RS_MNG_COL_ELEM_S* nextCol);
-static BOOLEAN _allowColSgi(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                            const RS_MNG_COL_ELEM_S* nextCol);
-static BOOLEAN _alloCol2xLTF(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                             const RS_MNG_COL_ELEM_S* nextCol);
-static BOOLEAN _allowColHe(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+static bool _allowColAnt(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                         const RS_MNG_COL_ELEM_S* nextCol);
+static bool _allowColMimo(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                          const RS_MNG_COL_ELEM_S* nextCol);
+static bool _allowColSiso(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                          const RS_MNG_COL_ELEM_S* nextCol);
+static bool _allowColSgi(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                         const RS_MNG_COL_ELEM_S* nextCol);
+static bool _alloCol2xLTF(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                          const RS_MNG_COL_ELEM_S* nextCol);
+static bool _allowColHe(const RS_MNG_STA_INFO_S* staInfo, U32 bw, const RS_MNG_COL_ELEM_S* nextCol);
+static bool _allowColHtVht(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
                            const RS_MNG_COL_ELEM_S* nextCol);
-static BOOLEAN _allowColHtVht(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                              const RS_MNG_COL_ELEM_S* nextCol);
 
 static const RS_MNG_COL_ELEM_S rsMngColumns[] = {
     [RS_MNG_COL_NON_HT_ANT_A] =
@@ -1059,18 +1116,20 @@
   rsMngRate->unset &= ~RS_MNG_RATE_GI;
 }
 
-static void _rsMngRateSetLdpc(RS_MNG_RATE_S* rsMngRate, BOOLEAN ldpc) {
+static void _rsMngRateSetLdpc(RS_MNG_RATE_S* rsMngRate, bool ldpc) {
   rsMngRate->rate.rate_n_flags &= ~RATE_MCS_LDPC_MSK;
   rsMngRate->rate.rate_n_flags |= (!!ldpc) << RATE_MCS_LDPC_POS;
   rsMngRate->unset &= ~RS_MNG_RATE_LDPC;
 }
 
+#if 0   // NEEDS_PORTING
 static BOOLEAN _rsMngRateGetStbc(const RS_MNG_RATE_S* rsMngRate) {
   _rsMngRateCheckSet(rsMngRate, RS_MNG_RATE_STBC);
   return !!(rsMngRate->rate.rate_n_flags & RATE_MCS_STBC_MSK);
 }
+#endif  // NEEDS_PORTING
 
-static void _rsMngRateSetStbc(RS_MNG_RATE_S* rsMngRate, BOOLEAN stbc) {
+static void _rsMngRateSetStbc(RS_MNG_RATE_S* rsMngRate, bool stbc) {
   WARN_ON(!(!stbc || !(rsMngRate->rate.rate_n_flags & RATE_MCS_HE_DCM_MSK)));
 
   rsMngRate->rate.rate_n_flags &= ~RATE_MCS_STBC_MSK;
@@ -1079,7 +1138,7 @@
   rsMngRate->unset |= RS_MNG_RATE_ANT;
 }
 
-static void _rsMngRateSetBfer(RS_MNG_RATE_S* rsMngRate, BOOLEAN bfer) {
+static void _rsMngRateSetBfer(RS_MNG_RATE_S* rsMngRate, bool bfer) {
   rsMngRate->rate.rate_n_flags &= ~RATE_MCS_BF_MSK;
   rsMngRate->rate.rate_n_flags |= (!!bfer) << RATE_MCS_BF_POS;
   rsMngRate->unset &= ~RS_MNG_RATE_BFER;
@@ -1161,7 +1220,7 @@
 static U16 _rsMngGetSupportedRatesByModeAndBw(const RS_MNG_STA_INFO_S* staInfo,
                                               RS_MNG_MODULATION_E modulation,
                                               TLC_MNG_CH_WIDTH_E bw) {
-  BOOLEAN isBw160;
+  bool isBw160;
   U32 supportedRates;
 
   if (modulation == RS_MNG_MODUL_LEGACY) {
@@ -1190,11 +1249,13 @@
   return (TLC_MNG_CH_WIDTH_E)staInfo->config.maxChWidth;
 }
 
+#if 0   // NEEDS_PORTING
 static BOOLEAN _rsMngAreAggsSupported(TLC_MNG_MODE_E bestSuppMode) {
   return bestSuppMode > TLC_MNG_MODE_LEGACY;
 }
+#endif  // NEEDS_PORTING
 
-static BOOLEAN _rsMngIsDcmSupported(const RS_MNG_STA_INFO_S* staInfo, BOOLEAN isMimo) {
+static bool _rsMngIsDcmSupported(const RS_MNG_STA_INFO_S* staInfo, bool isMimo) {
   if (isMimo) {
     return !!(staInfo->config.configFlags & TLC_MNG_CONFIG_FLAGS_HE_DCM_NSS_2_MSK);
   }
@@ -1202,6 +1263,7 @@
   return !!(staInfo->config.configFlags & TLC_MNG_CONFIG_FLAGS_HE_DCM_NSS_1_MSK);
 }
 
+#if 0   // NEEDS_PORTING
 static BOOLEAN _rsMngRateIsOptimal(const RS_MNG_STA_INFO_S* staInfo,
                                    const RS_MNG_RATE_S* rsMngRate) {
   U32 bw = _rsMngRateGetBw(rsMngRate);
@@ -1231,6 +1293,7 @@
 
   return TRUE;
 }
+#endif  // NEEDS_PORTING
 
 static U08 _rsMngGetHigherRateIdx(U08 initRateIdx, U32 supportedRatesMsk) {
   U32 tmpRateMsk;
@@ -1279,34 +1342,34 @@
 }
 
 // TODO - check. what if bt doesn't allow?
-static BOOLEAN _rsMngIsStbcSupported(const RS_MNG_STA_INFO_S* staInfo) {
+static bool _rsMngIsStbcSupported(const RS_MNG_STA_INFO_S* staInfo) {
   return !!(staInfo->config.configFlags & TLC_MNG_CONFIG_FLAGS_STBC_MSK);
 }
 
-static BOOLEAN _rsMngIsStbcAllowed(const RS_MNG_STA_INFO_S* staInfo, const RS_MNG_RATE_S* rate) {
+static bool _rsMngIsStbcAllowed(const RS_MNG_STA_INFO_S* staInfo, const RS_MNG_RATE_S* rate) {
   if ((iwl_mvm_get_valid_tx_ant(staInfo->mvm) & rsMngGetDualAntMsk()) != rsMngGetDualAntMsk()) {
-    return FALSE;
+    return false;
   }
   return _rsMngIsStbcSupported(staInfo) && !(rate->rate.rate_n_flags & RATE_MCS_HE_DCM_MSK);
 }
 
-static BOOLEAN _rsMngCoexIsLongAggAllowed(const RS_MNG_STA_INFO_S* staInfo) {
-  if (staInfo->config.band != NL80211_BAND_2GHZ) {
-    return TRUE;
+static bool _rsMngCoexIsLongAggAllowed(const RS_MNG_STA_INFO_S* staInfo) {
+  if (staInfo->config.band != WLAN_INFO_BAND_TWO_GHZ) {
+    return true;
   }
 
   if (btCoexManagerBtOwnsAnt(staInfo->mvm)) {
-    return FALSE;
+    return false;
   }
 
-  return TRUE;
+  return true;
 }
 
-static BOOLEAN _rsMngIsLdpcAllowed(const RS_MNG_STA_INFO_S* staInfo) {
+static bool _rsMngIsLdpcAllowed(const RS_MNG_STA_INFO_S* staInfo) {
   return !!(staInfo->config.configFlags & TLC_MNG_CONFIG_FLAGS_LDPC_MSK);
 }
 
-static BOOLEAN _rsMngIsAntSupported(const RS_MNG_STA_INFO_S* staInfo, U08 ant) {
+static bool _rsMngIsAntSupported(const RS_MNG_STA_INFO_S* staInfo, U08 ant) {
   return (ant & staInfo->config.chainsEnabled) == ant &&
          (iwl_mvm_get_valid_tx_ant(staInfo->mvm) & ant) == ant;
 }
@@ -1316,28 +1379,28 @@
 /*                            allowColFuncs                                         */
 /************************************************************************************/
 
-static BOOLEAN _allowColAnt(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                            const RS_MNG_COL_ELEM_S* nextCol) {
+static bool _allowColAnt(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                         const RS_MNG_COL_ELEM_S* nextCol) {
   if (!_rsMngIsAntSupported(staInfo, nextCol->ant)) {
-    return FALSE;
+    return false;
   }
 
   if (!_rsMngIsAntSupported(staInfo, (U08)(nextCol->ant ^ rsMngGetDualAntMsk()))) {
     // If the other antenna is disabled for some reason, this antenna is the only one allowed so
     // we must ignore possible BT-Coex restrictions. Also note that this function is only called
     // for siso columns, so nextCol->ant always has just one bit set so the xor makes sense.
-    return TRUE;
+    return true;
   }
 
-  if (staInfo->config.band != NL80211_BAND_2GHZ) {
-    return TRUE;
+  if (staInfo->config.band != WLAN_INFO_BAND_TWO_GHZ) {
+    return true;
   }
 
   if (btCoexManagerIsAntAvailable(staInfo->mvm, nextCol->ant)) {
-    return TRUE;
+    return true;
   }
 
-  return FALSE;
+  return false;
 }
 
 static U16 _rsMngGetAggTimeLimit(RS_MNG_STA_INFO_S* staInfo) {
@@ -1347,74 +1410,74 @@
   }
 
   if (_rsMngCoexIsLongAggAllowed(staInfo)) {
-    staInfo->longAggEnabled = TRUE;
+    staInfo->longAggEnabled = true;
     return RS_MNG_AGG_DURATION_LIMIT;
   }
 
-  staInfo->longAggEnabled = FALSE;
+  staInfo->longAggEnabled = false;
   return RS_MNG_AGG_DURATION_LIMIT_SHORT;
 }
 
-static BOOLEAN _allowColMimo(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                             const RS_MNG_COL_ELEM_S* nextCol) {
-  BOOLEAN isBw160 = (bw == TLC_MNG_CH_WIDTH_160MHZ);
+static bool _allowColMimo(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                          const RS_MNG_COL_ELEM_S* nextCol) {
+  bool isBw160 = (bw == TLC_MNG_CH_WIDTH_160MHZ);
 
   // TODO - check if ht/vht supported? redundent
   // if no mimo rate is supported
   if (!(staInfo->config.mcs[TLC_MNG_NSS_2][isBw160])) {
-    return FALSE;
+    return false;
   }
 
   if (staInfo->config.chainsEnabled != rsMngGetDualAntMsk()) {
-    return FALSE;
+    return false;
   }
 
   if (iwl_mvm_get_valid_tx_ant(staInfo->mvm) != rsMngGetDualAntMsk()) {
-    return FALSE;
+    return false;
   }
 
-  return TRUE;
+  return true;
 }
 
-static BOOLEAN _allowColSiso(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                             const RS_MNG_COL_ELEM_S* nextCol) {
-  BOOLEAN isBw160 = (bw == TLC_MNG_CH_WIDTH_160MHZ);
+static bool _allowColSiso(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                          const RS_MNG_COL_ELEM_S* nextCol) {
+  bool isBw160 = (bw == TLC_MNG_CH_WIDTH_160MHZ);
 
   // if there are supported SISO rates - return true. else - return false
   return (!!(staInfo->config.mcs[TLC_MNG_NSS_1][isBw160]));
 }
 
-static BOOLEAN _allowColHe(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                           const RS_MNG_COL_ELEM_S* nextCol) {
+static bool _allowColHe(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                        const RS_MNG_COL_ELEM_S* nextCol) {
   return !!(staInfo->config.bestSuppMode == TLC_MNG_MODE_HE);
 }
 
-static BOOLEAN _allowColHtVht(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                              const RS_MNG_COL_ELEM_S* nextCol) {
+static bool _allowColHtVht(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                           const RS_MNG_COL_ELEM_S* nextCol) {
   return !!(staInfo->config.bestSuppMode == TLC_MNG_MODE_HT ||
             staInfo->config.bestSuppMode == TLC_MNG_MODE_VHT);
 }
 
-static BOOLEAN _allowColSgi(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                            const RS_MNG_COL_ELEM_S* nextCol) {
+static bool _allowColSgi(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                         const RS_MNG_COL_ELEM_S* nextCol) {
   U08 sgiChWidthSupport = staInfo->config.sgiChWidthSupport;
 
   return !!(sgiChWidthSupport & BIT(bw));
 }
 
-static BOOLEAN _alloCol2xLTF(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
-                             const RS_MNG_COL_ELEM_S* nextCol) {
+static bool _alloCol2xLTF(const RS_MNG_STA_INFO_S* staInfo, U32 bw,
+                          const RS_MNG_COL_ELEM_S* nextCol) {
   return !(staInfo->config.configFlags & TLC_MNG_CONFIG_FLAGS_HE_BLOCK_2X_LTF_MSK);
 }
 
 /***************************************************************/
 
-static BOOLEAN _rsMngTpcIsActive(const RS_MNG_STA_INFO_S* staInfo) {
+static bool _rsMngTpcIsActive(const RS_MNG_STA_INFO_S* staInfo) {
   // There are 2 values for currStep that mean tpc isn't working currently - RS_MNG_TPC_INACTIVE
   // and RS_MNG_TPC_DISABLED.
   return staInfo->tpcTable.currStep < RS_MNG_TPC_NUM_STEPS;
 }
-static BOOLEAN _rsMngIsTestWindow(const RS_MNG_STA_INFO_S* staInfo) {
+static bool _rsMngIsTestWindow(const RS_MNG_STA_INFO_S* staInfo) {
   return staInfo->tryingRateUpscale || staInfo->searchBetterTbl || staInfo->tpcTable.testing;
 }
 
@@ -1422,9 +1485,14 @@
   lqCmd->agg_time_limit = cpu_to_le16(_rsMngGetAggTimeLimit(staInfo));
   lqCmd->agg_disable_start_th = RS_MNG_AGG_DISABLE_START_TH;
 
+#if 0  // NEEDS_PORTING
+  // TODO(fxbug.dev/79993): AMPDU Support
   // W/A for a HW bug that causes it to not prepare a second burst if the first one uses
   // all frames in the Fifo. W/A this by making sure there's always at least one frame left.
   lqCmd->agg_frame_cnt_limit = (U08)(staInfo->staBuffSize - 1);
+#else  // NEEDS_PORTING
+  lqCmd->agg_frame_cnt_limit = 1;
+#endif
 }
 
 // Get the next supported lower rate in the current column.
@@ -1442,6 +1510,8 @@
   return lowerSuppRateIdx;
 }
 
+#if 0   // NNEDS_PORTING
+// TODO(fxbug.dev/51295): Supports A-MSDU
 static void tlcMngNotifyAmsdu(const RS_MNG_STA_INFO_S* staInfo, U16 amsduSize, U16 tidBitmap) {
   int i;
 
@@ -1462,11 +1532,12 @@
     }
   }
 }
+#endif  // NEEDS_PORTING
 
 static void _rsMngFillNonHtRates(const RS_MNG_STA_INFO_S* staInfo, struct iwl_lq_cmd* lqCmd, U08 i,
                                  RS_MNG_RATE_S* rsMngRate) {
-  BOOLEAN togglingPossible = _rsMngIsAntSupported(staInfo, rsMngGetDualAntMsk()) &&
-                             btCoexManagerIsAntAvailable(staInfo->mvm, BT_COEX_SHARED_ANT_ID);
+  bool togglingPossible = _rsMngIsAntSupported(staInfo, rsMngGetDualAntMsk()) &&
+                          btCoexManagerIsAntAvailable(staInfo->mvm, BT_COEX_SHARED_ANT_ID);
 
   if (_rsMngRateGetMode(rsMngRate) != TLC_MNG_MODE_LEGACY) {
     U08 currIdx = _rsMngRateGetIdx(rsMngRate);
@@ -1474,8 +1545,8 @@
     _rsMngRateSetMode(rsMngRate, TLC_MNG_MODE_LEGACY);
     _rsMngRateSetModulation(rsMngRate, RS_MNG_MODUL_LEGACY);
     _rsMngRateSetBw(rsMngRate, CHANNEL_WIDTH20);
-    _rsMngRateSetLdpc(rsMngRate, FALSE);
-    _rsMngRateSetStbc(rsMngRate, FALSE);
+    _rsMngRateSetLdpc(rsMngRate, false);
+    _rsMngRateSetStbc(rsMngRate, false);
 
     // Always start with the non-shared antenna if it's available. If there's toggling, it
     // doesn't make much difference, and if there's no toggling due to bt-coex it promises we'll
@@ -1547,8 +1618,9 @@
   _rsMngFillNonHtRates(staInfo, lqCmd, i, &rsMngRate);
 }
 
-static void _rsMngFillLQCmd(RS_MNG_STA_INFO_S* staInfo, struct iwl_lq_cmd* lqCmd) {
+static void _rsMngFillLQCmd(RS_MNG_STA_INFO_S* staInfo) {
   int i;
+  struct iwl_lq_cmd* lqCmd = &staInfo->mvmsta->lq_sta.rs_drv.lq;
 
   memset(lqCmd, 0, sizeof(*lqCmd));
   lqCmd->sta_id = staInfo->mvmsta->sta_id;
@@ -1588,12 +1660,23 @@
       lqCmd->rs_table[i] |= cpu_to_le32(RATE_MCS_RTS_REQUIRED_MSK);
     }
   }
+
+  DBG_PRINTF(
+      UT, TLC_OFFLOAD_DBG, INFO,
+      "---------\nLQ cmd data\nsta_id: %02x\ntpc: %02x\nflags: %02x\nmimo_delim: %02x\nssam: "
+      "%02x\ndsam: %02x\ninit_rate_idx: %02x %02x %02x %02x\nagg_time_limit: %04x\nadsh: "
+      "%02x\nafcl: %02x\nreserved2: %08x\nrs_table first: %08x\nss_params: "
+      "%08x\n-----------",
+      lqCmd->sta_id, lqCmd->reduced_tpc, lqCmd->flags, lqCmd->mimo_delim,
+      lqCmd->single_stream_ant_msk, lqCmd->dual_stream_ant_msk, lqCmd->initial_rate_index[0],
+      lqCmd->initial_rate_index[1], lqCmd->initial_rate_index[2], lqCmd->initial_rate_index[3],
+      lqCmd->agg_time_limit, lqCmd->agg_disable_start_th, lqCmd->agg_frame_cnt_limit,
+      lqCmd->reserved2, cpu_to_le32(lqCmd->rs_table[0]), lqCmd->ss_params);
 }
 
 // rs_update_rate_tbl
-static void _rsMngUpdateRateTbl(RS_MNG_STA_INFO_S* staInfo, BOOLEAN notifyHost) {
-  _rsMngFillLQCmd(staInfo, &staInfo->mvmsta->lq_sta.rs_drv.lq);
-
+static void _rsMngUpdateRateTbl(RS_MNG_STA_INFO_S* staInfo) {
+  _rsMngFillLQCmd(staInfo);
   iwl_mvm_send_lq_cmd(staInfo->mvm, &staInfo->mvmsta->lq_sta.rs_drv.lq, !staInfo->enabled);
 }
 
@@ -1623,9 +1706,8 @@
   BUILD_BUG_ON(!((RS_MNG_COL_HE_1_6_SISO_ANT_A ^ RS_MNG_COL_HE_1_6_SISO_ANT_B) == 1));
   BUILD_BUG_ON(!((RS_MNG_COL_HE_0_8_SISO_ANT_A ^ RS_MNG_COL_HE_0_8_SISO_ANT_B) == 1));
 
-  DBG_PRINTF(UT, TLC_OFFLOAD_DBG, INFO,
-             "_rsMngSetVisitedColumn: colId %d, stbc allowed %d, visited columns 0x%x", colId,
-             _rsMngIsStbcSupported(staInfo), staInfo->visitedColumns);
+  IWL_DEBUG_RATE(NULL, "_rsMngSetVisitedColumn: colId %d, stbc allowed %d, visited columns 0x%x",
+                 colId, _rsMngIsStbcSupported(staInfo), staInfo->visitedColumns);
 
   staInfo->visitedColumns |= BIT(colId);
   if (rsMngColumns[colId].mode == RS_MNG_MODUL_SISO && _rsMngIsStbcSupported(staInfo)) {
@@ -1636,6 +1718,7 @@
              staInfo->visitedColumns);
 }
 
+#if 0  // NEEDS_PORTING
 static U32 _rsMngVhtRateToPhyRate(U32 bw, RS_MCS_E mcs, RS_MNG_GI_E gi, RS_MNG_MODULATION_E nss) {
   U32 bitrate;
 
@@ -1731,6 +1814,7 @@
   return 0;
 }
 
+
 static RS_MNG_TX_AMSDU_SIZE_E _rsMngAmsduSize(const RS_MNG_STA_INFO_S* staInfo, TLC_MNG_MODE_E mode,
                                               U32 bw, RS_MCS_E mcs, RS_MNG_GI_E gi,
                                               RS_MNG_MODULATION_E nss) {
@@ -2022,7 +2106,7 @@
 }
 
 // return: TRUE if there is a better start rate, so need to send LQ command
-// newIdx: valid only if the return value is true
+// newIdx: valid only if the return value is TRUE
 //        RS_MNG_INVALID_RATE_IDX - if need to keep using the current index
 //        new index to use        - if there is another rate that will provide better tpt / tpc
 static RS_MNG_ACTION_E _rsMngSearchBetterStartRate(const RS_MNG_STA_INFO_S* staInfo,
@@ -2373,6 +2457,7 @@
   // isUpscaleSearchCycle here is referring to the type of the previous search cycle.
   // This is here to prevent two consecutive upscale search cycles (i.e. started because of
   // passing the successFramesLimit threshold) within too short a time.
+  zx_time_t cur_time = zx::clock::get_monotonic().get();
   return staInfo->totalFramesSuccess > staLimits->successFramesLimit &&
          (!staInfo->isUpscaleSearchCycle ||
           time_after(jiffies,
@@ -2405,10 +2490,11 @@
 
   return FALSE;
 }
+#endif  // NEEDS_PORTING
 
 static void _rsMngPrepareForBwChangeAttempt(RS_MNG_STA_INFO_S* staInfo,
                                             const RS_MNG_RATE_S* rsMngRate) {
-  BOOLEAN isNonHt = _rsMngRateGetMode(rsMngRate) == TLC_MNG_MODE_LEGACY;
+  bool isNonHt = _rsMngRateGetMode(rsMngRate) == TLC_MNG_MODE_LEGACY;
   RS_MCS_E mcs;
   U32 bw;
   U32 isMimo;
@@ -2437,6 +2523,7 @@
   }
 }
 
+#if 0  // NEEDS_PORTING
 static void _rsMngSetStayInCol(RS_MNG_STA_INFO_S* staInfo) {
   staInfo->rsMngState = RS_MNG_STATE_STAY_IN_COLUMN;
   staInfo->stableColumn = staInfo->rateTblInfo.column;
@@ -2606,7 +2693,6 @@
                  "_rsMngTpcAllowed: TPC disallowed because amsdu is not yet active");
       return RS_MNG_TPC_DISALLOWED_AMSDU_INACTIVE;
     }
-
     if (time_before(jiffies,
                     staInfo->lastEnableJiffies + usecs_to_jiffies(RS_MNG_TPC_AMSDU_ENABLE))) {
       DBG_PRINTF(UT, TLC_OFFLOAD_DBG, INFO,
@@ -2646,7 +2732,7 @@
   }
 
   if (!tpcInactive) {
-    // if testing is true, then we are now operating on results from a tpc_action_increase test
+    // if testing is TRUE, then we are now operating on results from a tpc_action_increase test
     // window. In this case we don't want to completely disable tpc even if the current SR is
     // relatively bad. Instead we will just decrease back to the last good step.
     if (currStepSR <= RS_MNG_TPC_SR_DISABLE && !tpcTbl->testing) {
@@ -2854,7 +2940,7 @@
         updateLmac |= _rsMngStartSearchCycle(staInfo, currWin->averageTpt, &updateHost);
       } else {
         // Note that if the rate didn't really change the host-update function will not send
-        // a notification to host regadless of the value of this boolean.
+        // a notification to host regadless of the value of this BOOLEAN.
         updateHost = TRUE;
       }
     }
@@ -3272,9 +3358,10 @@
   return FALSE;
 }
 
+// TODO(fxbug.dev/84605): Rate Selection
 static void tlcStatUpdateHandler(RS_MNG_STA_INFO_S* staInfo, TLC_STAT_COMMON_API_S* stats,
                                  struct iwl_mvm* mvm, struct ieee80211_sta* sta, int tid,
-                                 bool is_ndp) {
+                                 BOOLEAN is_ndp) {
   BOOLEAN forceLmacUpdate = FALSE;
   int i;
 
@@ -3336,6 +3423,8 @@
   }
 }
 
+#endif  // NEEDS_PORTING
+
 /*********************************************************************/
 /*      External Functions + funcs used by them                      */
 /*********************************************************************/
@@ -3418,7 +3507,7 @@
   _rsMngRateSetMode(rsMngRate, mode);
   if (mode == TLC_MNG_MODE_LEGACY) {
     _rsMngRateSetModulation(rsMngRate, RS_MNG_MODUL_LEGACY);
-    _rsMngRateSetLdpc(rsMngRate, FALSE);
+    _rsMngRateSetLdpc(rsMngRate, false);
   } else {
     _rsMngRateSetModulation(rsMngRate, RS_MNG_MODUL_SISO);
     _rsMngRateSetLdpc(rsMngRate, _rsMngIsLdpcAllowed(staInfo));
@@ -3431,20 +3520,19 @@
   } else {
     _rsMngRateSetGi(rsMngRate, HT_VHT_NGI);
   }
-  _rsMngRateSetBfer(rsMngRate, FALSE);
+  _rsMngRateSetBfer(rsMngRate, false);
 
   if (mode > TLC_MNG_MODE_LEGACY && _rsMngIsStbcAllowed(staInfo, rsMngRate)) {
-    _rsMngRateSetStbc(rsMngRate, TRUE);
+    _rsMngRateSetStbc(rsMngRate, true);
     _rsMngRateSetAnt(rsMngRate, rsMngGetDualAntMsk());
   } else {
-    _rsMngRateSetStbc(rsMngRate, FALSE);
+    _rsMngRateSetStbc(rsMngRate, false);
     _rsMngRateSetAnt(rsMngRate, rsMngGetSingleAntMsk(staInfo->config.chainsEnabled));
   }
 
   if (mode > TLC_MNG_MODE_LEGACY) {
     U08 idx =
         (U08)(_rsMngGetSuppRatesSameMode(staInfo, rsMngRate) & BIT(RS_MCS_3) ? RS_MCS_3 : RS_MCS_0);
-
     _rsMngRateSetIdx(rsMngRate, idx);
   } else {
     if (LSB2ORD(nonHtRates) > RS_NON_HT_RATE_CCK_LAST) {
@@ -3472,25 +3560,25 @@
   tblInfo->column = _rsMngGetColByRate(&(tblInfo->rsMngRate));
   staInfo->stableColumn = tblInfo->column;
 
-  _rsMngUpdateRateTbl(staInfo, TRUE);
+  _rsMngUpdateRateTbl(staInfo);
 }
 
-static void rsMngResetStaInfo(struct iwl_mvm* mvm, struct ieee80211_sta* sta,
-                              struct iwl_mvm_sta* mvmsta, RS_MNG_STA_INFO_S* staInfo,
-                              BOOLEAN reconfigure) {
+static void rsMngResetStaInfo(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta,
+                              RS_MNG_STA_INFO_S* staInfo, bool reconfigure) {
   U32 fixedRate = staInfo->fixedRate;
   U16 aggDurationLimit = staInfo->aggDurationLimit;
   U08 amsduInAmpdu = staInfo->amsduInAmpdu;
-  BOOLEAN longAggEnabled = staInfo->longAggEnabled;
+  bool longAggEnabled = staInfo->longAggEnabled;
 
   _memclr(staInfo, sizeof(*staInfo));
 
+  zx_time_t cur_time = zx_clock_get_monotonic();
   staInfo->mvm = mvm;
-  staInfo->sta = sta;
+  // staInfo->sta = sta;
   staInfo->mvmsta = mvmsta;
-  staInfo->lastSearchCycleEndTimeJiffies = jiffies;
-  staInfo->lastRateUpscaleTimeJiffies = jiffies;
-  staInfo->lastEnableJiffies = jiffies;
+  staInfo->lastSearchCycleEndTimestamp = cur_time;
+  staInfo->lastRateUpscaleTimestamp = cur_time;
+  staInfo->lastEnableTimestamp = cur_time;
 
   if (reconfigure) {
     staInfo->fixedRate = fixedRate;
@@ -3511,7 +3599,7 @@
   staInfo->tpcTable.currStep = RS_MNG_TPC_DISABLED;
   staInfo->staBuffSize = RS_MNG_AGG_FRAME_CNT_LIMIT;
   staInfo->amsduEnabledSize = RS_MNG_AMSDU_INVALID;
-  staInfo->amsduSupport = FALSE;
+  staInfo->amsduSupport = false;
   staInfo->failSafeCounter = 0;
   staInfo->amsduBlacklist = 0;
 }
@@ -3614,9 +3702,9 @@
 /*                        Cmd Handlers                                */
 /**********************************************************************/
 
-static void cmdHandlerTlcMngConfig(struct iwl_mvm* mvm, struct ieee80211_sta* sta,
-                                   struct iwl_mvm_sta* mvmsta, RS_MNG_STA_INFO_S* staInfo,
-                                   TLC_MNG_CONFIG_PARAMS_CMD_API_S* config, BOOLEAN reconfigure) {
+void cmdHandlerTlcMngConfig(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta,
+                            RS_MNG_STA_INFO_S* staInfo, TLC_MNG_CONFIG_PARAMS_CMD_API_S* config,
+                            bool reconfigure) {
   if (!_tlcMngConfigValid(config)) {
     return;
   }
@@ -3634,14 +3722,15 @@
     }
   }
 
-  rsMngResetStaInfo(mvm, sta, mvmsta, staInfo, staInfo->enabled && reconfigure);
+  rsMngResetStaInfo(mvm, mvmsta, staInfo, staInfo->enabled && reconfigure);
   BUILD_BUG_ON(sizeof(staInfo->config) != sizeof(*config));
   memcpy(&staInfo->config, config, sizeof(staInfo->config));
 
-  rsMngInitAmsdu(staInfo);
+  // TODO(fxbug.dev/51295): Supports A-MSDU
+  // rsMngInitAmsdu(staInfo);
 
   // send LQ command with basic rates table
   _rsMngTlcInit(staInfo);
 
-  staInfo->enabled = TRUE;
+  staInfo->enabled = true;
 }
diff --git a/third_party/iwlwifi/mvm/rs-ng.c b/third_party/iwlwifi/mvm/rs-ng.c
index a643be6..b43c446 100644
--- a/third_party/iwlwifi/mvm/rs-ng.c
+++ b/third_party/iwlwifi/mvm/rs-ng.c
@@ -33,9 +33,12 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *****************************************************************************/
+#include "_rateScaleMng.h"
 #include "mvm.h"
 #include "rs.h"
 
+#if 0   // NEEDS_PORTING
+
 static void iwl_start_agg(struct iwl_mvm* mvm, struct ieee80211_sta* sta, int tid) {
   struct iwl_mvm_sta* mvmsta = iwl_mvm_sta_from_mac80211(sta);
   struct iwl_mvm_tid_data* tid_data;
@@ -52,16 +55,17 @@
     }
   }
 }
+#endif  // NEEDS_PORTING
 
-static uint8_t rs_fw_bw_from_sta_bw(struct ieee80211_sta* sta) {
-  switch (sta->bandwidth) {
-    case IEEE80211_STA_RX_BW_160:
+static uint8_t rs_fw_bw_from_sta_bw(struct iwl_mvm_sta* mvm_sta) {
+  switch (mvm_sta->bw) {
+    case CHANNEL_BANDWIDTH_CBW160:
       return IWL_TLC_MNG_CH_WIDTH_160MHZ;
-    case IEEE80211_STA_RX_BW_80:
+    case CHANNEL_BANDWIDTH_CBW80:
       return IWL_TLC_MNG_CH_WIDTH_80MHZ;
-    case IEEE80211_STA_RX_BW_40:
+    case CHANNEL_BANDWIDTH_CBW40:
       return IWL_TLC_MNG_CH_WIDTH_40MHZ;
-    case IEEE80211_STA_RX_BW_20:
+    case CHANNEL_BANDWIDTH_CBW20:
     default:
       return IWL_TLC_MNG_CH_WIDTH_20MHZ;
   }
@@ -83,38 +87,57 @@
   return fw_chains;
 }
 
-static uint8_t rs_fw_sgi_cw_support(struct ieee80211_sta* sta) {
-  struct ieee80211_sta_ht_cap* ht_cap = &sta->ht_cap;
-  struct ieee80211_sta_vht_cap* vht_cap = &sta->vht_cap;
-  struct ieee80211_sta_he_cap* he_cap = &sta->he_cap;
+static uint8_t rs_fw_sgi_cw_support(struct iwl_mvm_sta* mvm_sta) {
+  struct ieee80211_ht_capabilities* ht_cap = &mvm_sta->ht_cap;
   uint8_t supp = 0;
 
+#if 0  // TODO(fxbug.dev/84773): Support HE (802.11ax)
+  struct ieee80211_sta_he_cap* he_cap = &sta->he_cap;
+
   if (he_cap && he_cap->has_he) {
     return 0;
   }
+#endif
 
-  if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) {
+  if (ht_cap->ht_capability_info & IEEE80211_HT_CAP_SGI_20) {
     supp |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ);
   }
-  if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) {
+  if (ht_cap->ht_capability_info & IEEE80211_HT_CAP_SGI_40) {
     supp |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ);
   }
+
+#if 0  // TODO(fxbug.dev/36684): Support VHT (802.11ac)
+  struct ieee80211_sta_vht_cap* vht_cap = &mvm_sta->vht_cap;
+
   if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80) {
     supp |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ);
   }
   if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160) {
     supp |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ);
   }
+#endif
 
   return supp;
 }
 
-static uint16_t rs_fw_set_config_flags(struct iwl_mvm* mvm, struct ieee80211_sta* sta) {
-  struct ieee80211_sta_ht_cap* ht_cap = &sta->ht_cap;
+static uint16_t rs_fw_set_config_flags(struct iwl_mvm* mvm, struct iwl_mvm_sta* sta) {
+  struct ieee80211_ht_capabilities* ht_cap = &sta->ht_cap;
+
+  uint16_t flags = 0;
+
+  if (mvm->cfg->ht_params->stbc && (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1)) {
+    if ((ht_cap && (ht_cap->ht_capability_info & IEEE80211_HT_CAP_RX_STBC))) {
+      flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK;
+    }
+  }
+
+#if 0  // NEEDS_PORTING
+  // The following code needs porting when VHT and HE are supported.
+  // TODO(fxbug.dev/84773): Support HE (802.11ax)
+  // TODO(fxbug.dev/36684): Support VHT (802.11ac)
   struct ieee80211_sta_vht_cap* vht_cap = &sta->vht_cap;
   struct ieee80211_sta_he_cap* he_cap = &sta->he_cap;
   bool vht_ena = vht_cap && vht_cap->vht_supported;
-  uint16_t flags = 0;
 
   if (mvm->cfg->ht_params->stbc && (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1)) {
     if (he_cap && he_cap->has_he) {
@@ -140,10 +163,13 @@
       (he_cap->he_cap_elem.phy_cap_info[3] & IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK)) {
     flags |= IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK;
   }
+#endif
 
   return flags;
 }
 
+#if 0  // NEEDS_PORTING
+// TODO(fxbug.dev/36684): Support VHT (802.11ac)
 static int rs_fw_vht_highest_rx_mcs_index(const struct ieee80211_sta_vht_cap* vht_cap, int nss) {
   uint16_t rx_mcs = le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map) & (0x3 << (2 * (nss - 1)));
   rx_mcs >>= (2 * (nss - 1));
@@ -223,77 +249,95 @@
   }
 }
 
-static void rs_fw_set_supp_rates(struct ieee80211_sta* sta, struct ieee80211_supported_band* sband,
+#endif  // NEEDS_PORTING
+
+static void rs_fw_set_supp_rates(struct iwl_mvm_sta* mvm_sta,
                                  TLC_MNG_CONFIG_PARAMS_CMD_API_S* cmd) {
-  int i;
-  unsigned long tmp;
-  unsigned long supp;
-  const struct ieee80211_sta_ht_cap* ht_cap = &sta->ht_cap;
-  const struct ieee80211_sta_vht_cap* vht_cap = &sta->vht_cap;
-  const struct ieee80211_sta_he_cap* he_cap = &sta->he_cap;
+  uint16_t i;
+  uint64_t nonht_rates = 0;
+  uint8_t* supported_rates = mvm_sta->supp_rates;
+  const struct ieee80211_ht_capabilities* ht_cap = &mvm_sta->ht_cap;
+
+#if 0   // NEEDS_PORTING
+  // TODO(fxbug.dev/84773): Support HE (802.11ax)
+  const struct ieee80211_he_capabilities* he_cap = &sta->he_cap;
+#endif  // NEEDS_PORTING
 
   /* non HT rates */
-  supp = 0;
-  tmp = sta->supp_rates[sband->band];
-  for_each_set_bit(i, &tmp, BITS_PER_LONG) supp |= BIT(sband->bitrates[i].hw_value);
+  // We got the supported rates from MLME, and filt out the non-HT rates here.
+  for (i = 0; i < WLAN_MAC_MAX_RATES; i++) {
+    if (supported_rates[i] <= 54 && supported_rates[i] != 0)
+      nonht_rates |= nonht_rate_to_bit(supported_rates[i]);
+  }
 
-  cmd->nonHt = supp;
+  cmd->nonHt = nonht_rates;
   cmd->bestSuppMode = IWL_TLC_MNG_MODE_NON_HT;
 
   /* HT/VHT rates */
+#if 0   // NEEDS_PORTING
+  // TODO(fxbug.dev/84773): Support HE (802.11ax)
   if (he_cap && he_cap->has_he) {
     cmd->bestSuppMode = IWL_TLC_MNG_MODE_HE;
     rs_fw_he_set_enabled_rates(sta, he_cap, cmd);
-  } else if (vht_cap && vht_cap->vht_supported) {
+  } else
+
+  // TODO(fxbug.dev/36684): Support VHT (802.11ac)
+  const struct ieee80211_vht_capabilities* vht_cap = &sta->vht_cap;
+
+  if (vht_cap) {
     cmd->bestSuppMode = IWL_TLC_MNG_MODE_VHT;
     rs_fw_vht_set_enabled_rates(sta, vht_cap, cmd);
-  } else if (ht_cap && ht_cap->ht_supported) {
+  } else
+#endif  // NEEDS_PORTING
+  if (ht_cap) {
     cmd->bestSuppMode = IWL_TLC_MNG_MODE_HT;
-    cmd->mcs[0][0] = (ht_cap->mcs.rx_mask[0]);
-    cmd->mcs[1][0] = (ht_cap->mcs.rx_mask[1]);
+    cmd->mcs[0][0] = (ht_cap->supported_mcs_set.bytes[0]);
+    cmd->mcs[1][0] = (ht_cap->supported_mcs_set.bytes[1]);
   }
 }
 
-/// TODO: merge file?
-#include "rateScaleMng.c"
+static void rs_drv_rate_init(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvm_sta, bool update) {
+  struct iwl_lq_sta* lq_sta = &mvm_sta->lq_sta.rs_drv;
+  struct iwl_mvm_vif* mvm_vif = mvm_sta->mvmvif;
 
-static void rs_drv_rate_init(struct iwl_mvm* mvm, struct ieee80211_sta* sta, enum nl80211_band band,
-                             bool update) {
-  struct ieee80211_hw* hw = mvm->hw;
-  struct iwl_mvm_sta* mvmsta = iwl_mvm_sta_from_mac80211(sta);
-  struct iwl_lq_sta* lq_sta = &mvmsta->lq_sta.rs_drv;
-  struct ieee80211_supported_band* sband;
   RS_MNG_STA_INFO_S* staInfo = &lq_sta->pers;
   TLC_MNG_CONFIG_PARAMS_CMD_API_S config = {};
 
 #ifdef CPTCFG_IWLWIFI_DEBUGFS
   iwl_mvm_reset_frame_stats(mvm);
 #endif
-  sband = hw->wiphy->bands[band];
 
-  mvmsta->amsdu_enabled = 0;
-  mvmsta->max_amsdu_len = sta->max_amsdu_len;
-
-  config.maxChWidth = update ? rs_fw_bw_from_sta_bw(sta) : IWL_TLC_MNG_CH_WIDTH_20MHZ;
-  config.configFlags = rs_fw_set_config_flags(mvm, sta);
+  config.maxChWidth = rs_fw_bw_from_sta_bw(mvm_sta);
+  config.configFlags = rs_fw_set_config_flags(mvm, mvm_sta);
   config.chainsEnabled = rs_fw_set_active_chains(iwl_mvm_get_valid_tx_ant(mvm));
-  config.maxMpduLen = sta->max_amsdu_len;
-  config.sgiChWidthSupport = rs_fw_sgi_cw_support(sta);
+
+  config.sgiChWidthSupport = rs_fw_sgi_cw_support(mvm_sta);
   config.amsduSupported = iwl_mvm_is_csum_supported(mvm);
-  config.band = sband->band;
-  rs_fw_set_supp_rates(sta, sband, &config);
 
-  cmdHandlerTlcMngConfig(mvm, sta, mvmsta, staInfo, &config, update);
+  mvm_sta->amsdu_enabled = 0;
+#if 0  // TODO(fxbug.dev/49528): Support Aggregation
+  mvm_sta->max_amsdu_len = sta->max_amsdu_len;
+  config.maxMpduLen = sta->max_amsdu_len;
+#endif
+
+  config.band = mvm_vif->phy_ctxt->band;
+  rs_fw_set_supp_rates(mvm_sta, &config);
+
+  cmdHandlerTlcMngConfig(mvm, mvm_sta, staInfo, &config, update);
 }
 
-void iwl_mvm_rs_rate_init(struct iwl_mvm* mvm, struct ieee80211_sta* sta, enum nl80211_band band,
-                          bool update) {
+void iwl_mvm_rs_rate_init(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvm_sta, bool update) {
+#if 0  // NEEDS PORTING
   if (iwl_mvm_has_tlc_offload(mvm)) {
-    rs_fw_rate_init(mvm, sta, band, update);
+    rs_fw_rate_init(mvm, mvm_sta, band, update);
   } else {
-    rs_drv_rate_init(mvm, sta, band, update);
+    rs_drv_rate_init(mvm, mvm_sta, band, update);
   }
+#endif
+  // Only use driver rate initialization before supporting tlc offload for new firmwares.
+  rs_drv_rate_init(mvm, mvm_sta, update);
 }
+#if 0  // NEEDS_PORTING
 
 void iwl_mvm_rs_tx_status(struct iwl_mvm* mvm, struct ieee80211_sta* sta, int tid,
                           struct ieee80211_tx_info* info, bool is_ndp) {
@@ -477,3 +521,5 @@
 
 void rs_update_last_rssi(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta,
                          struct ieee80211_rx_status* rx_status) {}
+
+#endif  // NEEDS_PORTING
diff --git a/third_party/iwlwifi/mvm/rs-ng.h b/third_party/iwlwifi/mvm/rs-ng.h
index 4afe94b..4d49c34 100644
--- a/third_party/iwlwifi/mvm/rs-ng.h
+++ b/third_party/iwlwifi/mvm/rs-ng.h
@@ -36,21 +36,14 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_RS_NG_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_RS_NG_H_
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
 
 #include "third_party/iwlwifi/iwl-trans.h"
-#include "third_party/iwlwifi/mvm/fw-api.h"
-
-#define U08 uint8_t
-#define U16 uint16_t
-#define U32 uint32_t
-#define U64 uint64_t
-#define INLINE inline
-
 #include "third_party/iwlwifi/mvm/API_rates.h"
 #include "third_party/iwlwifi/mvm/_rateScaleMng.h"
 #include "third_party/iwlwifi/mvm/apiGroupDatapath.h"
 #include "third_party/iwlwifi/mvm/apiVersion.h"
+#include "third_party/iwlwifi/mvm/fw-api.h"
 #include "third_party/iwlwifi/platform/kernel.h"
 
 #define RS_NAME "iwlwifi-rs"
@@ -92,8 +85,7 @@
 int iwl_mvm_rate_control_register(void);
 void iwl_mvm_rate_control_unregister(void);
 int iwl_mvm_tx_protection(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta, bool enable);
-void iwl_mvm_rs_rate_init(struct iwl_mvm* mvm, struct ieee80211_sta* sta, wlan_info_band_t band,
-                          bool update);
+void iwl_mvm_rs_rate_init(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvm_sta, bool update);
 void iwl_mvm_rs_tx_status(struct iwl_mvm* mvm, struct ieee80211_sta* sta, int tid,
                           struct ieee80211_tx_info* info, bool is_ndp);
 #ifdef CPTCFG_IWLWIFI_DEBUGFS
@@ -101,7 +93,7 @@
 #endif
 
 void iwl_mvm_tlc_update_notif(struct iwl_mvm* mvm, struct iwl_rx_cmd_buffer* rxb);
-void rs_fw_rate_init(struct iwl_mvm* mvm, struct ieee80211_sta* sta, wlan_info_band_t band,
+void rs_fw_rate_init(struct iwl_mvm* mvm, struct iwl_mvm_sta* sta, wlan_info_band_t band,
                      bool update);
 void iwl_mvm_rs_add_sta(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta);
 int rs_fw_tx_protection(struct iwl_mvm* mvm, struct iwl_mvm_sta* mvmsta, bool enable);
diff --git a/third_party/iwlwifi/mvm/rx.c b/third_party/iwlwifi/mvm/rx.c
index 013a772..4ebe0e0 100644
--- a/third_party/iwlwifi/mvm/rx.c
+++ b/third_party/iwlwifi/mvm/rx.c
@@ -38,7 +38,7 @@
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/mvm/fw-api.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 /*
  * iwl_mvm_rx_rx_phy_cmd - REPLY_RX_PHY_CMD handler
  *
@@ -274,7 +274,7 @@
   }
 
   wlan_info_band_t band =
-      phy_flags & RX_RES_PHY_FLAGS_BAND_24 ? WLAN_INFO_BAND_2GHZ : WLAN_INFO_BAND_5GHZ;
+      phy_flags & RX_RES_PHY_FLAGS_BAND_24 ? WLAN_INFO_BAND_TWO_GHZ : WLAN_INFO_BAND_FIVE_GHZ;
   rx_info.channel.primary = le16_to_cpu(phy_info->channel);
 
 #if 0   // NEEDS_PORTING
@@ -433,7 +433,7 @@
 #endif  // NEEDS_PORTING
   } else {
     rx_info.phy =
-        phy_flags & RX_RES_PHY_FLAGS_MOD_CCK ? WLAN_INFO_PHY_TYPE_CCK : WLAN_INFO_PHY_TYPE_OFDM;
+        phy_flags & RX_RES_PHY_FLAGS_MOD_CCK ? WLAN_INFO_PHY_TYPE_HR : WLAN_INFO_PHY_TYPE_OFDM;
 
     int mac80211_idx;
     zx_status_t status = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, band, &mac80211_idx);
@@ -488,7 +488,7 @@
       .mac_frame_size = res_len,
       .info = rx_info,
   };
-  wlanmac_ifc_recv(&mvm->mvmvif[0]->ifc, &rx_packet);
+  wlan_softmac_ifc_recv(&mvm->mvmvif[0]->ifc, &rx_packet);
 
 #if 0   // NEEDS_PORTING
   if (take_ref) {
diff --git a/third_party/iwlwifi/mvm/rxmq.c b/third_party/iwlwifi/mvm/rxmq.c
index 2cccb58..512e7a5 100644
--- a/third_party/iwlwifi/mvm/rxmq.c
+++ b/third_party/iwlwifi/mvm/rxmq.c
@@ -36,7 +36,8 @@
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/mvm/fw-api.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/rcu.h"
 
 static bool is_multicast_ether_addr(uint8_t addr[6]) { return (addr[0] & 0x1) != 0; }
 
@@ -77,10 +78,8 @@
 
   keyidx = stats->extiv[3] >> 6;
 
-  mtx_lock(&mvmsta->ptk_pn_mutex);
-  ptk_pn = rcu_dereference(mvmsta->ptk_pn[keyidx]);
+  ptk_pn = iwl_rcu_load(mvmsta->ptk_pn[keyidx]);
   if (!ptk_pn) {
-    mtx_unlock(&mvmsta->ptk_pn_mutex);
     return ZX_ERR_BAD_STATE;
   }
 
@@ -92,30 +91,26 @@
 
   /* we don't use HCCA/802.11 QoS TSPECs, so drop such frames */
   if (tid >= IWL_MAX_TID_COUNT) {
-    mtx_unlock(&mvmsta->ptk_pn_mutex);
     return ZX_ERR_NOT_SUPPORTED;
   }
 
   /* load pn */
-  pn[0] = stats->extiv[7];
-  pn[1] = stats->extiv[6];
-  pn[2] = stats->extiv[5];
-  pn[3] = stats->extiv[4];
-  pn[4] = stats->extiv[1];
-  pn[5] = stats->extiv[0];
+  pn[0] = stats->extiv[0];
+  pn[1] = stats->extiv[1];
+  pn[2] = stats->extiv[4];
+  pn[3] = stats->extiv[5];
+  pn[4] = stats->extiv[6];
+  pn[5] = stats->extiv[7];
 
   res = memcmp(pn, ptk_pn->q[queue].pn[tid], IEEE80211_CCMP_PN_LEN);
   if (res < 0) {
-    mtx_unlock(&mvmsta->ptk_pn_mutex);
     return ZX_ERR_INVALID_ARGS;
   }
   if (!res && !(stats->flag & RX_FLAG_ALLOW_SAME_PN)) {
-    mtx_unlock(&mvmsta->ptk_pn_mutex);
     return ZX_ERR_INVALID_ARGS;
   }
 
   memcpy(ptk_pn->q[queue].pn[tid], pn, IEEE80211_CCMP_PN_LEN);
-  mtx_unlock(&mvmsta->ptk_pn_mutex);
 
   stats->flag |= RX_FLAG_PN_VALIDATED;
 
@@ -218,12 +213,12 @@
 
   // Send to MLME
   // TODO(fxbug.dev/43218) Need to revisit to handle multiple IFs
-    wlan_rx_packet_t rx_packet = {
+  wlan_rx_packet_t rx_packet = {
       .mac_frame_buffer = (uint8_t*)frame,
       .mac_frame_size = frame_len,
       .info = rx_status->rx_info,
   };
-  wlanmac_ifc_recv(&mvm->mvmvif[0]->ifc, &rx_packet);
+  wlan_softmac_ifc_recv(&mvm->mvmvif[0]->ifc, &rx_packet);
 }
 
 static int iwl_mvm_get_signal_strength(struct iwl_mvm* mvm, int energy_a, int energy_b) {
@@ -320,8 +315,10 @@
         return 0;
 #endif  // NEEDS_PORTING
     default:
-        /* Expected in monitor (not having the keys) */
-        if (!mvm->monitor_on) { IWL_ERR(mvm, "Unhandled alg: 0x%x\n", status); }
+      /* Expected in monitor (not having the keys) */
+      if (!mvm->monitor_on) {
+        IWL_ERR(mvm, "Unhandled alg: 0x%x\n", status);
+      }
   }
 
   return 0;
@@ -1384,22 +1381,20 @@
     }
   }
 
-  rcu_read_lock();
+  iwl_rcu_read_lock(mvm->dev);
 
   if (desc->status & cpu_to_le16(IWL_RX_MPDU_STATUS_SRC_STA_FOUND)) {
     uint8_t id = desc->sta_id_flags & IWL_RX_MPDU_SIF_STA_ID_MASK;
 
     if (!WARN_ON_ONCE(id >= ARRAY_SIZE(mvm->fw_id_to_mac_id))) {
-      sta = rcu_dereference(mvm->fw_id_to_mac_id[id]);
+      sta = iwl_rcu_load(mvm->fw_id_to_mac_id[id]);
     }
   } else if (!is_multicast_ether_addr(hdr->addr2)) {
     /*
      * This is fine since we prevent two stations with the same
      * address from being added.
      */
-    mtx_lock(&mvm->mutex);
     sta = iwl_mvm_find_sta_by_addr(mvm, hdr->addr2);
-    mtx_unlock(&mvm->mutex);
   }
 
 #if 0  // NEEDS_PORTING
@@ -1535,7 +1530,7 @@
     }
     // rx_status->rate_idx = rate;
     rx_status.rx_info.phy =
-        phy_info & RX_RES_PHY_FLAGS_MOD_CCK ? WLAN_INFO_PHY_TYPE_CCK : WLAN_INFO_PHY_TYPE_OFDM;
+        phy_info & RX_RES_PHY_FLAGS_MOD_CCK ? WLAN_INFO_PHY_TYPE_HR : WLAN_INFO_PHY_TYPE_OFDM;
   }
   rx_status.rx_info.valid_fields |= WLAN_RX_INFO_VALID_DATA_RATE;
 
@@ -1560,7 +1555,7 @@
     iwl_mvm_pass_packet_to_mac80211(mvm, hdr, len, &rx_status, queue, sta);
   }
 out:
-  rcu_read_unlock();
+  iwl_rcu_read_unlock(mvm->dev);
 }
 
 void iwl_mvm_rx_monitor_ndp(struct iwl_mvm* mvm, struct napi_struct* napi,
diff --git a/third_party/iwlwifi/mvm/scan.c b/third_party/iwlwifi/mvm/scan.c
index 532d78d..9723afc 100644
--- a/third_party/iwlwifi/mvm/scan.c
+++ b/third_party/iwlwifi/mvm/scan.c
@@ -154,7 +154,7 @@
 }
 
 static inline __le32 iwl_mvm_scan_rxon_flags(wlan_info_band_t band) {
-  if (band == WLAN_INFO_BAND_2GHZ) {
+  if (band == WLAN_INFO_BAND_TWO_GHZ) {
     return cpu_to_le32(PHY_BAND_24);
   } else {
     return cpu_to_le32(PHY_BAND_5);
@@ -168,7 +168,7 @@
   iwl_mvm_toggle_tx_ant(mvm, &mvm->scan_last_antenna_idx);
   tx_ant = BIT(mvm->scan_last_antenna_idx) << RATE_MCS_ANT_POS;
 
-  if (band == WLAN_INFO_BAND_2GHZ && !no_cck) {
+  if (band == WLAN_INFO_BAND_TWO_GHZ && !no_cck) {
     return cpu_to_le32(IWL_RATE_1M_PLCP | RATE_MCS_CCK_MSK | tx_ant);
   } else {
     return cpu_to_le32(IWL_RATE_6M_PLCP | tx_ant);
@@ -371,11 +371,9 @@
   }
 }
 
-static void notify_mlme_scan_completion(struct iwl_mvm_vif* mvmvif, bool successful) {
-  wlan_hw_scan_result_t scan_result = {
-      .code = successful ? WLAN_HW_SCAN_SUCCESS : WLAN_HW_SCAN_ABORTED,
-  };
-  wlanmac_ifc_hw_scan_complete(&mvmvif->ifc, &scan_result);
+static void notify_mlme_scan_completion(struct iwl_mvm_vif* mvmvif, zx_status_t status) {
+  // TODO(fxbug.dev/88934): scan_id is always 0
+  wlan_softmac_ifc_scan_complete(&mvmvif->ifc, status, 0);
 }
 
 void iwl_mvm_rx_lmac_scan_complete_notif(struct iwl_mvm* mvm, struct iwl_rx_cmd_buffer* rxb) {
@@ -454,7 +452,7 @@
 
     mvm->scan_status &= ~IWL_MVM_SCAN_REGULAR;
     if (mvm->scan_vif) {
-      notify_mlme_scan_completion(mvm->scan_vif, !aborted);
+      notify_mlme_scan_completion(mvm->scan_vif, aborted ? ZX_ERR_CANCELED : ZX_OK);
     } else {
       IWL_WARN(mvm, "mvm->scan_vif is not registered, but got a SCAN completion\n");
     }
@@ -629,11 +627,11 @@
 static void iwl_mvm_scan_fill_tx_cmd(struct iwl_mvm* mvm, struct iwl_scan_req_tx_cmd* tx_cmd,
                                      bool no_cck) {
   tx_cmd[0].tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_BT_DIS);
-  tx_cmd[0].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, WLAN_INFO_BAND_2GHZ, no_cck);
+  tx_cmd[0].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, WLAN_INFO_BAND_TWO_GHZ, no_cck);
   tx_cmd[0].sta_id = mvm->aux_sta.sta_id;
 
   tx_cmd[1].tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_BT_DIS);
-  tx_cmd[1].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, WLAN_INFO_BAND_5GHZ, no_cck);
+  tx_cmd[1].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, WLAN_INFO_BAND_FIVE_GHZ, no_cck);
   tx_cmd[1].sta_id = mvm->aux_sta.sta_id;
 }
 
@@ -886,8 +884,8 @@
 
   cmd->scan_flags = cpu_to_le32(iwl_mvm_scan_lmac_flags(mvm, params));
 
-  cmd->flags = iwl_mvm_scan_rxon_flags(params->channels[0] <= 14 ? WLAN_INFO_BAND_2GHZ
-                                                                 : WLAN_INFO_BAND_5GHZ);
+  cmd->flags = iwl_mvm_scan_rxon_flags(params->channels[0] <= 14 ? WLAN_INFO_BAND_TWO_GHZ
+                                                                 : WLAN_INFO_BAND_FIVE_GHZ);
   cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP | MAC_FILTER_IN_BEACON);
   iwl_mvm_scan_fill_tx_cmd(mvm, cmd->tx_cmd, params->no_cck);
 #if 0   // NEEDS_PORTING
@@ -959,11 +957,11 @@
   uint16_t rates = 0;
   int i;
 
-  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_2GHZ];
+  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_TWO_GHZ];
   for (i = 0; i < band->n_bitrates; i++) {
     rates |= rate_to_scan_rate_flag(iwl_get_rate_index(band->bitrates[i]));
   }
-  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_5GHZ];
+  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_FIVE_GHZ];
   for (i = 0; i < band->n_bitrates; i++) {
     rates |= rate_to_scan_rate_flag(iwl_get_rate_index(band->bitrates[i]));
   }
@@ -985,11 +983,11 @@
   struct ieee80211_supported_band* band;
   int i, j = 0;
 
-  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_2GHZ];
+  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_TWO_GHZ];
   for (i = 0; i < band->n_channels; i++, j++) {
     channels[j] = band->channels[i].ch_num;
   }
-  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_5GHZ];
+  band = &mvm->nvm_data->bands[WLAN_INFO_BAND_FIVE_GHZ];
   for (i = 0; i < band->n_channels; i++, j++) {
     channels[j] = band->channels[i].ch_num;
   }
@@ -1029,8 +1027,8 @@
   if (iwl_mvm_is_cdb_supported(mvm)) {
     enum iwl_mvm_scan_type lb_type, hb_type;
 
-    lb_type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_2GHZ);
-    hb_type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_5GHZ);
+    lb_type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_TWO_GHZ);
+    hb_type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_FIVE_GHZ);
 
     cfg->out_of_channel_time[SCAN_LB_LMAC_IDX] = cpu_to_le32(scan_timing[lb_type].max_out_time);
     cfg->suspend_time[SCAN_LB_LMAC_IDX] = cpu_to_le32(scan_timing[lb_type].suspend_time);
@@ -1063,8 +1061,8 @@
   };
   enum iwl_mvm_scan_type type = IWL_SCAN_TYPE_NOT_SET;
   enum iwl_mvm_scan_type hb_type = IWL_SCAN_TYPE_NOT_SET;
-  uint32_t num_channels = mvm->nvm_data->bands[WLAN_INFO_BAND_2GHZ].n_channels +
-                          mvm->nvm_data->bands[WLAN_INFO_BAND_5GHZ].n_channels;
+  uint32_t num_channels = mvm->nvm_data->bands[WLAN_INFO_BAND_TWO_GHZ].n_channels +
+                          mvm->nvm_data->bands[WLAN_INFO_BAND_FIVE_GHZ].n_channels;
   uint32_t flags;
   uint8_t channel_flags;
 
@@ -1073,8 +1071,8 @@
   }
 
   if (iwl_mvm_is_cdb_supported(mvm)) {
-    type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_2GHZ);
-    hb_type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_5GHZ);
+    type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_TWO_GHZ);
+    hb_type = iwl_mvm_get_scan_type_band(mvm, WLAN_INFO_BAND_FIVE_GHZ);
     if (type == mvm->scan_type && hb_type == mvm->hb_scan_type) {
       return ZX_OK;
     }
@@ -1455,7 +1453,7 @@
   return ZX_OK;
 }
 
-#if 0  // NEEDS_PORTING
+#if 0   // NEEDS_PORTING
 static int iwl_mvm_num_scans(struct iwl_mvm* mvm) {
     return hweight32(mvm->scan_status & IWL_MVM_SCAN_MASK);
 }
@@ -1541,7 +1539,7 @@
 
   mvm->scan_status &= ~IWL_MVM_SCAN_REGULAR;
   if (mvm->scan_vif) {
-    notify_mlme_scan_completion(mvm->scan_vif, false);
+    notify_mlme_scan_completion(mvm->scan_vif, ZX_ERR_TIMED_OUT);
   } else {
     IWL_ERR(mvm, "mvm->scan_vif is not registered, but got a SCAN timeout\n");
   }
@@ -1561,8 +1559,17 @@
 }
 #endif  // NEEDS_PORTING
 
-zx_status_t iwl_mvm_reg_scan_start(struct iwl_mvm_vif* mvmvif,
-                                   const wlan_hw_scan_config_t* scan_config) {
+// TODO(fxbug.dev/89682): Fuchsia-specific wlan_softmac_passive_scan_args_t should be moved out
+// of these function arguments or this function should be moved to platform/mvm-mlme.cc.
+zx_status_t iwl_mvm_reg_scan_start_passive(
+    struct iwl_mvm_vif* mvmvif, const wlan_softmac_passive_scan_args_t* passive_scan_args) {
+  // TODO(fxbug.dev/89693): iwlwifi only uses the channels field.
+  return iwl_mvm_reg_scan_start(mvmvif, passive_scan_args->channels_list,
+                                passive_scan_args->channels_count);
+}
+
+zx_status_t iwl_mvm_reg_scan_start(struct iwl_mvm_vif* mvmvif, const uint8_t* channels_list,
+                                   size_t channels_count) {
   struct iwl_mvm* mvm = mvmvif->mvm;
   struct iwl_host_cmd hcmd = {
       .len =
@@ -1607,6 +1614,8 @@
     return ZX_ERR_SHOULD_WAIT;
   }
 
+// TODO(fxbug.dev/89683): The number of SSIDs and channels is not actually limitless
+// and should be checked.
 #if 0   // NEEDS_PORTING
     if (!iwl_mvm_scan_fits(mvm, req->n_ssids, ies, req->n_channels)) { return ZX_ERR_BUFFER_TOO_SMALL; }
 #endif  // NEEDS_PORTING
@@ -1619,9 +1628,9 @@
       .delay = 0,
   };
 
-  params.n_channels = scan_config->num_channels;
+  params.n_channels = channels_count;
   for (uint32_t i = 0; i < params.n_channels; ++i) {
-    params.channels[i] = scan_config->channels[i];
+    params.channels[i] = channels_list[i];
   }
 
 #if 0   // NEEDS_PORTING
@@ -1817,7 +1826,7 @@
     }
 
     if (mvm->scan_vif != NULL) {
-      notify_mlme_scan_completion(mvm->scan_vif, !aborted);
+      notify_mlme_scan_completion(mvm->scan_vif, aborted ? ZX_ERR_CANCELED : ZX_OK);
     }
 
     mvm->scan_vif = NULL;
diff --git a/third_party/iwlwifi/mvm/sta.c b/third_party/iwlwifi/mvm/sta.c
index 5075699..602de19 100644
--- a/third_party/iwlwifi/mvm/sta.c
+++ b/third_party/iwlwifi/mvm/sta.c
@@ -39,12 +39,13 @@
 
 #include "third_party/iwlwifi/mvm/mvm.h"
 #include "third_party/iwlwifi/mvm/rs.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/rcu.h"
 
 static zx_status_t iwl_mvm_set_fw_key_idx(struct iwl_mvm* mvm);
 
 static zx_status_t iwl_mvm_send_sta_key(struct iwl_mvm* mvm, uint32_t sta_id,
-                                        const struct iwl_mvm_sta_key_conf* keyconf, bool mcast,
+                                        const struct ieee80211_key_conf* keyconf, bool mcast,
                                         uint32_t tkip_iv32, uint16_t* tkip_p1k, uint32_t cmd_flags,
                                         uint8_t key_offset, bool mfp);
 
@@ -66,7 +67,7 @@
 // Note that in order to avoid race condition, the mvm->mutex must be hold before calling this
 // function, and cannot be released before adding new STA to mvm->fw_id_to_mac_id[].
 //
-static int iwl_mvm_find_free_sta_id(struct iwl_mvm* mvm, wlan_info_mac_role_t mac_role) {
+static int iwl_mvm_find_free_sta_id(struct iwl_mvm* mvm, wlan_mac_role_t mac_role) {
   uint32_t reserved_ids = 0;
 
   BUILD_BUG_ON(IWL_MVM_STATION_COUNT > 32);
@@ -75,17 +76,17 @@
   iwl_assert_lock_held(&mvm->mutex);
 
   /* d0i3/d3 assumes the AP's sta_id (of sta vif) is 0. reserve it. */
-  if (mac_role != WLAN_INFO_MAC_ROLE_CLIENT) {
+  if (mac_role != WLAN_MAC_ROLE_CLIENT) {
     reserved_ids = BIT(0);
   }
 
-  // find an empty slot in mvm->fw_id_to_mac_id array.
+  /* Don't take rcu_read_lock() since we are protected by mvm->mutex */
   for (size_t sta_id = 0; sta_id < ARRAY_SIZE(mvm->fw_id_to_mac_id); sta_id++) {
     if (BIT(sta_id) & reserved_ids) {
       continue;
     }
 
-    if (!mvm->fw_id_to_mac_id[sta_id]) {
+    if (!iwl_rcu_load(mvm->fw_id_to_mac_id[sta_id])) {
       return sta_id;
     }
   }
@@ -124,29 +125,13 @@
     }
   }
 
-#if 1  // NEEDS_PORTING
-  add_sta_cmd.station_flags |=
-      cpu_to_le32(STA_FLG_MIMO_EN_SISO) | cpu_to_le32(STA_FLG_FAT_EN_20MHZ);
-#else
+#if 0  // NEEDS_PORTING
+// TODO(fxbug.dev/51295): Supports A-MSDU
+  mpdu_dens = sta->ht_cap.ampdu_density;
   uint32_t agg_size = 0, mpdu_dens = 0;
-  switch (sta->bandwidth) {
-    case IEEE80211_STA_RX_BW_160:
-      add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_160MHZ);
-    /* fall through */
-    case IEEE80211_STA_RX_BW_80:
-      add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_80MHZ);
-    /* fall through */
-    case IEEE80211_STA_RX_BW_40:
-      add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ);
-    /* fall through */
-    case IEEE80211_STA_RX_BW_20:
-      if (sta->ht_cap.ht_supported) {
-        add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_20MHZ);
-      }
-      break;
-  }
 
-  switch (sta->rx_nss) {
+  //TODO(fxbug.dev/91457): Use real NSS data for filling the flag.
+  switch (mvm_sta->rx_nss) {
     case 1:
       add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO);
       break;
@@ -175,14 +160,8 @@
       /* nothing */
       break;
   }
-
-  if (sta->ht_cap.ht_supported) {
-    add_sta_cmd.station_flags_msk |=
-        cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | STA_FLG_AGG_MPDU_DENS_MSK);
-
-    mpdu_dens = sta->ht_cap.ampdu_density;
-  }
-
+  
+  // TODO(fxbug.dev/36684): Support VHT (802.11ac)
   if (sta->vht_cap.vht_supported) {
     agg_size = sta->vht_cap.cap & IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
     agg_size >>= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT;
@@ -214,6 +193,34 @@
     add_sta_cmd.uapsd_acs |= add_sta_cmd.uapsd_acs << 4;
     add_sta_cmd.sp_length = sta->max_sp ? sta->max_sp * 2 : 128;
   }
+
+#else
+  add_sta_cmd.station_flags |=
+      cpu_to_le32(STA_FLG_MIMO_EN_SISO) | cpu_to_le32(STA_FLG_FAT_EN_20MHZ);
+
+  add_sta_cmd.station_flags_msk |=
+      cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | STA_FLG_AGG_MPDU_DENS_MSK);
+
+  switch (mvm_sta->bw) {
+    add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_160MHZ);
+    __attribute__((fallthrough));
+    case CHANNEL_BANDWIDTH_CBW80:
+      add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_80MHZ);
+      __attribute__((fallthrough));
+    case CHANNEL_BANDWIDTH_CBW40:
+      add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ);
+      __attribute__((fallthrough));
+    case CHANNEL_BANDWIDTH_CBW20:
+      if (mvm_sta->support_ht) {
+        add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_20MHZ);
+      }
+      break;
+    default:
+      IWL_WARN(NULL, "No bandwidth from station is indicated, hardcode it to 40mhz.");
+      add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ);
+  }
+  add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO2);
+
 #endif  // NEEDS_PORTING
 
   status = ADD_STA_SUCCESS;
@@ -1668,7 +1675,7 @@
     goto err;
   }
 
-  if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_CLIENT) {
+  if (mvmvif->mac_role == WLAN_MAC_ROLE_CLIENT) {
     if (!mvm_sta->tdls) {
       if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) {
         IWL_WARN(mvmvif, "mvmvif->ap_sta_id is invalid\n");
@@ -1681,7 +1688,7 @@
     }
   }
 
-  mvmvif->mvm->fw_id_to_mac_id[sta_id] = mvm_sta;
+  iwl_rcu_store(mvmvif->mvm->fw_id_to_mac_id[sta_id], mvm_sta);
   ret = ZX_OK;
 
 err:
@@ -1690,10 +1697,9 @@
 
 struct iwl_mvm_sta* iwl_mvm_find_sta_by_addr(struct iwl_mvm* mvm, uint8_t addr[ETH_ALEN]) {
   struct iwl_mvm_sta* sta = NULL;
-  iwl_assert_lock_held(&mvm->mutex);
 
   for (size_t sta_id = 0; sta_id < ARRAY_SIZE(mvm->fw_id_to_mac_id); sta_id++) {
-    sta = mvm->fw_id_to_mac_id[sta_id];
+    sta = iwl_rcu_load(mvm->fw_id_to_mac_id[sta_id]);
     if (sta == NULL) {
       continue;
     }
@@ -1750,7 +1756,7 @@
 
   iwl_assert_lock_held(&mvm->mutex);
 
-  struct iwl_mvm_sta* mvm_sta = mvm->fw_id_to_mac_id[sta_id];
+  struct iwl_mvm_sta* mvm_sta = iwl_rcu_load(mvm->fw_id_to_mac_id[sta_id]);
 
   /* Note: internal stations are marked as error values */
   if (!mvm_sta) {
@@ -1870,7 +1876,7 @@
     *status = IWL_MVM_QUEUE_FREE;
   }
 
-  if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_CLIENT && mvmvif->ap_sta_id == sta_id) {
+  if (mvmvif->mac_role == WLAN_MAC_ROLE_CLIENT && mvmvif->ap_sta_id == sta_id) {
     /* if associated - we can't remove the AP STA now */
     if (mvmvif->bss_conf.assoc) {
       IWL_WARN(mvmvif, "Ignore the AP station removal since it is still associated\n");
@@ -1905,6 +1911,7 @@
 #endif  // NEEDS_PORTING
 
   ret = iwl_mvm_rm_sta_common(mvm, mvm_sta->sta_id);
+  iwl_rcu_store(mvm->fw_id_to_mac_id[mvm_sta->sta_id], NULL);
 
   return ret;
 }
@@ -3057,7 +3064,7 @@
 #endif  // NEEDS_PORTING
 
 static zx_status_t iwl_mvm_send_sta_key(struct iwl_mvm* mvm, uint32_t sta_id,
-                                        const struct iwl_mvm_sta_key_conf* key, bool mcast,
+                                        const struct ieee80211_key_conf* key, bool mcast,
                                         uint32_t tkip_iv32, uint16_t* tkip_p1k, uint32_t cmd_flags,
                                         uint8_t key_offset, bool mfp) {
   union {
@@ -3082,7 +3089,7 @@
   key_flags = cpu_to_le16(keyidx);
   key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_KEY_MAP);
 
-  switch (key->cipher_type) {
+  switch (key->cipher) {
     case CIPHER_SUITE_TYPE_CCMP_128:
       key_flags |= cpu_to_le16(STA_KEY_FLG_CCM);
       memcpy(u.cmd.common.key, key->key, key->keylen);
@@ -3124,21 +3131,20 @@
   return ret;
 }
 
-static int iwl_mvm_send_sta_igtk(struct iwl_mvm* mvm, const struct iwl_mvm_sta_key_conf* keyconf,
+static int iwl_mvm_send_sta_igtk(struct iwl_mvm* mvm, const struct ieee80211_key_conf* keyconf,
                                  uint8_t sta_id, bool remove_key) {
   struct iwl_mvm_mgmt_mcast_key_cmd igtk_cmd = {};
 
   /* verify the key details match the required command's expectations */
   if (WARN_ON((keyconf->key_type == WLAN_KEY_TYPE_PAIRWISE) ||
               (keyconf->keyidx != 4 && keyconf->keyidx != 5) ||
-              (keyconf->cipher_type != CIPHER_SUITE_TYPE_BIP_CMAC_128 &&
-               keyconf->cipher_type != CIPHER_SUITE_TYPE_BIP_GMAC_128 &&
-               keyconf->cipher_type != CIPHER_SUITE_TYPE_BIP_GMAC_256))) {
+              (keyconf->cipher != CIPHER_SUITE_TYPE_BIP_CMAC_128 &&
+               keyconf->cipher != CIPHER_SUITE_TYPE_BIP_GMAC_128 &&
+               keyconf->cipher != CIPHER_SUITE_TYPE_BIP_GMAC_256))) {
     return ZX_ERR_INVALID_ARGS;
   }
 
-  if (WARN_ON(!iwl_mvm_has_new_rx_api(mvm) &&
-              keyconf->cipher_type != CIPHER_SUITE_TYPE_BIP_CMAC_128)) {
+  if (WARN_ON(!iwl_mvm_has_new_rx_api(mvm) && keyconf->cipher != CIPHER_SUITE_TYPE_BIP_CMAC_128)) {
     return ZX_ERR_INVALID_ARGS;
   }
 
@@ -3148,7 +3154,7 @@
   if (remove_key) {
     igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_NOT_VALID);
   } else {
-    switch (keyconf->cipher_type) {
+    switch (keyconf->cipher) {
       case CIPHER_SUITE_TYPE_BIP_CMAC_128:
         igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_FLG_CCM);
         break;
@@ -3161,7 +3167,7 @@
     }
 
     memcpy(igtk_cmd.igtk, keyconf->key, keyconf->keylen);
-    if (keyconf->cipher_type == CIPHER_SUITE_TYPE_BIP_GMAC_256) {
+    if (keyconf->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_256) {
       igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_FLG_KEY_32BYTES);
     }
     igtk_cmd.receive_seq_cnt = cpu_to_le64(keyconf->rx_seq);
@@ -3204,7 +3210,7 @@
 
 static zx_status_t __iwl_mvm_set_sta_key(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvmvif,
                                          struct iwl_mvm_sta* mvmsta,
-                                         const struct iwl_mvm_sta_key_conf* keyconf,
+                                         const struct ieee80211_key_conf* keyconf,
                                          uint8_t key_offset, bool mcast) {
   zx_status_t ret = ZX_OK;
   uint32_t sta_id;
@@ -3214,15 +3220,15 @@
 
   if (mvmsta) {
     sta_id = mvmsta->sta_id;
-  } else if (mvmvif->mac_role == WLAN_INFO_MAC_ROLE_AP &&
-             keyconf->key_type != WLAN_KEY_TYPE_PAIRWISE) {
+    mfp = (keyconf->key_type == WLAN_KEY_TYPE_IGTK);
+  } else if (mvmvif->mac_role == WLAN_MAC_ROLE_AP && keyconf->key_type != WLAN_KEY_TYPE_PAIRWISE) {
     sta_id = mvmvif->mcast_sta.sta_id;
   } else {
     IWL_ERR(mvm, "Failed to find station id\n");
     return ZX_ERR_INVALID_ARGS;
   }
 
-  switch (keyconf->cipher_type) {
+  switch (keyconf->cipher) {
     case CIPHER_SUITE_TYPE_CCMP_128:
       ret = iwl_mvm_send_sta_key(mvm, sta_id, keyconf, mcast, 0, NULL, 0, key_offset, mfp);
       break;
@@ -3234,8 +3240,7 @@
 }
 
 static zx_status_t __iwl_mvm_remove_sta_key(struct iwl_mvm* mvm, uint8_t sta_id,
-                                            const struct iwl_mvm_sta_key_conf* keyconf,
-                                            uint8_t key_offset, bool mcast) {
+                                            const struct ieee80211_key_conf* keyconf, bool mcast) {
   union {
     struct iwl_mvm_add_sta_key_cmd_v1 cmd_v1;
     struct iwl_mvm_add_sta_key_cmd cmd;
@@ -3264,7 +3269,7 @@
    * of the command, so we can do this union trick.
    */
   u.cmd.common.key_flags = key_flags;
-  u.cmd.common.key_offset = key_offset;
+  u.cmd.common.key_offset = keyconf->hw_key_idx;
   u.cmd.common.sta_id = sta_id;
 
   size = new_api ? sizeof(u.cmd) : sizeof(u.cmd_v1);
@@ -3286,37 +3291,28 @@
 }
 
 zx_status_t iwl_mvm_set_sta_key(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvmvif,
-                                struct iwl_mvm_sta* mvmsta,
-                                const struct iwl_mvm_sta_key_conf* keyconf, uint8_t key_offset) {
-  bool mcast = keyconf->key_type != WLAN_KEY_TYPE_PAIRWISE;
+                                struct iwl_mvm_sta* mvmsta, struct ieee80211_key_conf* keyconf,
+                                uint8_t key_offset) {
+  bool mcast = keyconf->key_type == WLAN_KEY_TYPE_GROUP;
   uint8_t sta_id = IWL_MVM_INVALID_STA;
   zx_status_t ret = ZX_OK;
   static const uint8_t __maybe_unused zero_addr[ETH_ALEN] = {0};
 
   iwl_assert_lock_held(&mvm->mutex);
 
-  if (mvmvif->mac_role != WLAN_INFO_MAC_ROLE_AP || keyconf->key_type == WLAN_KEY_TYPE_PAIRWISE) {
+  if (mvmvif->mac_role != WLAN_MAC_ROLE_AP || keyconf->key_type == WLAN_KEY_TYPE_PAIRWISE) {
     sta_id = mvmsta->sta_id;
 
   } else {
     sta_id = mvmvif->mcast_sta.sta_id;
   }
 
-  if (keyconf->cipher_type == CIPHER_SUITE_TYPE_BIP_CMAC_128 ||
-      keyconf->cipher_type == CIPHER_SUITE_TYPE_BIP_GMAC_128 ||
-      keyconf->cipher_type == CIPHER_SUITE_TYPE_BIP_GMAC_256) {
+  if (keyconf->cipher == CIPHER_SUITE_TYPE_BIP_CMAC_128 ||
+      keyconf->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_128 ||
+      keyconf->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_256) {
     ret = iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, false);
     goto end;
   }
-  // TODO(fxbug.dev/86728): remove the WPA2 key workaround
-  key_offset = keyconf->keyidx;
-  if (mvm->active_key_list[key_offset].keylen) {
-    // delete the last key if present
-    if (iwl_mvm_remove_sta_key(mvmvif, mvmsta, &mvm->active_key_list[key_offset]) != ZX_OK) {
-      IWL_WARN(mvm, "Unable to delete key at offset %d", key_offset);
-    }
-    memset(&mvm->active_key_list[key_offset], 0, sizeof(struct iwl_mvm_sta_key_conf));
-  }
 
   /* If the key_offset is not pre-assigned, we need to find a
    * new offset to use.  In normal cases, the offset is not
@@ -3334,6 +3330,7 @@
     if (key_offset == STA_KEY_IDX_INVALID) {
       return ZX_ERR_NO_SPACE;
     }
+    keyconf->hw_key_idx = key_offset;
   }
 
   ret = __iwl_mvm_set_sta_key(mvm, mvmvif, mvmsta, keyconf, key_offset, mcast);
@@ -3347,34 +3344,29 @@
    * to the same key slot (offset).
    * If this fails, remove the original as well.
    */
-  if ((keyconf->cipher_type == CIPHER_SUITE_TYPE_WEP_40 ||
-       keyconf->cipher_type == CIPHER_SUITE_TYPE_WEP_104) &&
+  if ((keyconf->cipher == CIPHER_SUITE_TYPE_WEP_40 ||
+       keyconf->cipher == CIPHER_SUITE_TYPE_WEP_104) &&
       mvmsta) {
     ret = __iwl_mvm_set_sta_key(mvm, mvmvif, mvmsta, keyconf, key_offset, !mcast);
     if (ret != ZX_OK) {
-      __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, key_offset, mcast);
+      __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast);
       goto end;
     }
   }
 
   __set_bit(key_offset, mvm->fw_key_table);
-  // TODO(fxbug.dev/86728): remove the WPA2 key workaround
-  // Save the keyconf in the driver key table for easier deleteion
-  mvm->active_key_list[key_offset] = *keyconf;
 
 end:
-  IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%zu idx=%d mvmsta=%pM ret=%d\n", keyconf->cipher_type,
+  IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%zu idx=%d mvmsta=%pM ret=%d\n", keyconf->cipher,
                 keyconf->keylen, keyconf->keyidx, mvmsta ? mvmsta->addr : zero_addr, ret);
   return ret;
 }
 
-zx_status_t iwl_mvm_remove_sta_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta,
-                                   struct iwl_mvm_sta_key_conf* keyconf) {
-  struct iwl_mvm* mvm = mvmvif->mvm;
+zx_status_t iwl_mvm_remove_sta_key(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvmvif,
+                                   struct iwl_mvm_sta* mvm_sta,
+                                   const struct ieee80211_key_conf* keyconf) {
   bool mcast = keyconf->key_type != WLAN_KEY_TYPE_PAIRWISE;
   uint8_t sta_id = IWL_MVM_INVALID_STA;
-  // TODO(fxbug.dev/86728): remove the WPA2 key workaround
-  int hw_key_idx = keyconf->keyidx;
   int i;
   zx_status_t ret;
 
@@ -3384,16 +3376,14 @@
 
   IWL_INFO(mvm, "mvm remove dynamic key: idx=%d sta=%d\n", keyconf->keyidx, sta_id);
 
-#if 0   // NEEDS_PORTING
-  if (mvm_sta && (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC ||
-                  keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 ||
-                  keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256)) {
+  if (mvm_sta && (keyconf->cipher == CIPHER_SUITE_TYPE_BIP_CMAC_128 ||
+                  keyconf->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_128 ||
+                  keyconf->cipher == CIPHER_SUITE_TYPE_BIP_GMAC_256)) {
     return iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, true);
   }
-#endif  // NEEDS_PORTING
 
-  if (!test_and_clear_bit(hw_key_idx, mvm->fw_key_table)) {
-    IWL_ERR(mvm, "offset %d not used in fw key table.\n", hw_key_idx);
+  if (!test_and_clear_bit(keyconf->hw_key_idx, mvm->fw_key_table)) {
+    IWL_ERR(mvm, "offset %d not used in fw key table.\n", keyconf->hw_key_idx);
     return ZX_ERR_BAD_HANDLE;
   }
 
@@ -3403,22 +3393,21 @@
       mvm->fw_key_deleted[i]++;
     }
   }
-  mvm->fw_key_deleted[hw_key_idx] = 0;
+  mvm->fw_key_deleted[keyconf->hw_key_idx] = 0;
 
   if (!mvm_sta) {
     IWL_DEBUG_WEP(mvm, "station non-existent, early return.\n");
     return ZX_OK;
   }
 
-  ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, hw_key_idx, mcast);
+  ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast);
   if (ret != ZX_OK) {
     return ret;
   }
 
   /* delete WEP key twice to get rid of (now useless) offset */
-  if (keyconf->cipher_type == CIPHER_SUITE_TYPE_WEP_40 ||
-      keyconf->cipher_type == CIPHER_SUITE_TYPE_WEP_104) {
-    ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, hw_key_idx, !mcast);
+  if (keyconf->cipher == CIPHER_SUITE_TYPE_WEP_40 || keyconf->cipher == CIPHER_SUITE_TYPE_WEP_104) {
+    ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, !mcast);
   }
 
   return ret;
diff --git a/third_party/iwlwifi/mvm/sta.h b/third_party/iwlwifi/mvm/sta.h
index a12e6b4..3780ede 100644
--- a/third_party/iwlwifi/mvm/sta.h
+++ b/third_party/iwlwifi/mvm/sta.h
@@ -37,13 +37,12 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_STA_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_MVM_STA_H_
 
-#include <fuchsia/hardware/wlan/info/c/banjo.h>
+#include <fuchsia/hardware/wlan/associnfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
 #include <fuchsia/wlan/ieee80211/c/banjo.h>
 #include <threads.h>
 #include <zircon/types.h>
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
-
 /* for IWL_MAX_TID_COUNT */
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/mvm/rs.h"
@@ -324,6 +323,7 @@
 struct iwl_mvm_key_pn {
   struct rcu_head rcu_head;
   struct {
+    /* Stored in packet byte order (little-endian) */
     uint8_t pn[IWL_MAX_TID_COUNT][IEEE80211_CCMP_PN_LEN];
   } ____cacheline_aligned_in_smp q[];
 };
@@ -359,19 +359,6 @@
 };
 
 /**
- * struct iwl_mvm_sta_key_conf - per station key configuration data
- */
-struct iwl_mvm_sta_key_conf {
-  atomic64_t tx_pn;
-  uint64_t rx_seq;
-  cipher_suite_type_t cipher_type;
-  wlan_key_type_t key_type;
-  uint8_t keyidx;
-  size_t keylen;
-  uint8_t key[0];
-};
-
-/**
  * struct iwl_mvm_sta - representation of a station in the driver
  * @sta_id: the index of the station in the fw (will be replaced by id_n_color)
  * @tfd_queue_msk: the tfd queues used by the station
@@ -436,8 +423,6 @@
     struct iwl_lq_sta rs_drv;
   } lq_sta;
   struct iwl_mvm_vif* mvmvif;
-  struct iwl_mvm_sta_key_conf* key_conf;
-  mtx_t ptk_pn_mutex;
   struct iwl_mvm_key_pn __rcu* ptk_pn[4];
   struct iwl_mvm_rxq_dup_data* dup_data;
 
@@ -465,6 +450,14 @@
   uint8_t addr[ETH_ALEN];
 
   bool tdls;  // not really used, but add here to make code compile
+
+  channel_bandwidth_t bw;
+  bool support_ht;
+  struct ieee80211_ht_capabilities ht_cap;
+  bool support_vht;
+  struct ieee80211_vht_capabilities vht_cap;
+
+  uint8_t supp_rates[WLAN_MAC_MAX_RATES];
 };
 
 uint16_t iwl_mvm_tid_queued(struct iwl_mvm* mvm, struct iwl_mvm_tid_data* tid_data);
@@ -510,10 +503,11 @@
 zx_status_t iwl_mvm_rm_sta(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta);
 int iwl_mvm_rm_sta_id(struct iwl_mvm* mvm, struct ieee80211_vif* vif, uint8_t sta_id);
 int iwl_mvm_set_sta_key(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvm_vif,
-                        struct iwl_mvm_sta* mvm_sta, const struct iwl_mvm_sta_key_conf* key_conf,
+                        struct iwl_mvm_sta* mvm_sta, struct ieee80211_key_conf* key_conf,
                         uint8_t key_offset);
-zx_status_t iwl_mvm_remove_sta_key(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvmsta,
-                                   struct iwl_mvm_sta_key_conf* keyconf);
+zx_status_t iwl_mvm_remove_sta_key(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvmvif,
+                                   struct iwl_mvm_sta* mvmsta,
+                                   const struct ieee80211_key_conf* keyconf);
 
 void iwl_mvm_update_tkip_key(struct iwl_mvm* mvm, struct ieee80211_vif* vif,
                              struct ieee80211_key_conf* keyconf, struct ieee80211_sta* sta,
@@ -547,7 +541,7 @@
 int iwl_mvm_add_mcast_sta(struct iwl_mvm* mvm, struct ieee80211_vif* vif);
 int iwl_mvm_rm_mcast_sta(struct iwl_mvm* mvm, struct ieee80211_vif* vif);
 int iwl_mvm_allocate_int_sta(struct iwl_mvm* mvm, struct iwl_mvm_int_sta* sta, uint32_t qmask,
-                             wlan_info_mac_role_t iftype, enum iwl_sta_type type);
+                             wlan_mac_role_t iftype, enum iwl_sta_type type);
 void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm* mvm, struct ieee80211_vif* vif);
 void iwl_mvm_dealloc_int_sta(struct iwl_mvm* mvm, struct iwl_mvm_int_sta* sta);
 int iwl_mvm_add_snif_sta(struct iwl_mvm* mvm, struct ieee80211_vif* vif);
diff --git a/third_party/iwlwifi/mvm/time-event.c b/third_party/iwlwifi/mvm/time-event.c
index 9b5d233..29e3233 100644
--- a/third_party/iwlwifi/mvm/time-event.c
+++ b/third_party/iwlwifi/mvm/time-event.c
@@ -163,26 +163,28 @@
 out_unlock:
   rcu_read_unlock();
 }
+#endif  // NEEDS_PORTING
 
-static bool iwl_mvm_te_check_disconnect(struct iwl_mvm* mvm, struct ieee80211_vif* vif,
+static bool iwl_mvm_te_check_disconnect(struct iwl_mvm* mvm, struct iwl_mvm_vif* mvmvif,
                                         const char* errmsg) {
-  struct iwl_mvm_vif* mvmvif = iwl_mvm_vif_from_mac80211(vif);
-
-  if (vif->type != NL80211_IFTYPE_STATION) {
+  if (mvmvif->mac_role != WLAN_MAC_ROLE_CLIENT) {
     return false;
   }
 
-  if (!mvmvif->csa_bcn_pending && vif->bss_conf.assoc && vif->bss_conf.dtim_period) {
+  if (!mvmvif->csa_bcn_pending && mvmvif->bss_conf.assoc && mvmvif->bss_conf.dtim_period) {
     return false;
   }
   if (errmsg) {
     IWL_ERR(mvm, "%s\n", errmsg);
   }
 
+#if 0   // NEEDS_PORTING
   iwl_mvm_connection_loss(mvm, vif, errmsg);
+#endif  // NEEDS_PORTING
   return true;
 }
 
+#if 0   // NEEDS_PORTING
 static void iwl_mvm_te_handle_notify_csa(struct iwl_mvm* mvm,
                                          struct iwl_mvm_time_event_data* te_data,
                                          struct iwl_time_event_notif* notif) {
@@ -247,6 +249,7 @@
     break;
   }
 }
+#endif  // NEEDS_PORTING
 
 /*
  * Handles a FW notification for an event that is known to the driver.
@@ -262,7 +265,9 @@
   IWL_DEBUG_TE(mvm, "Handle time event notif - UID = 0x%x action %d\n",
                le32_to_cpu(notif->unique_id), le32_to_cpu(notif->action));
 
+#if 0   // NEEDS_PORTING
   iwl_mvm_te_check_trigger(mvm, notif, te_data);
+#endif  // NEEDS_PORTING
 
   /*
    * The FW sends the start/end time event notifications even for events
@@ -283,28 +288,30 @@
 
     IWL_DEBUG_TE(mvm, "%s\n", msg);
 
-    if (iwl_mvm_te_check_disconnect(mvm, te_data->vif, msg)) {
+    if (iwl_mvm_te_check_disconnect(mvm, te_data->mvmvif, msg)) {
       iwl_mvm_te_clear_data(mvm, te_data);
       return;
     }
   }
 
   if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_END) {
-    IWL_DEBUG_TE(mvm, "TE ended - current time %lu, estimated end %lu\n", jiffies,
-                 te_data->end_jiffies);
+    IWL_DEBUG_TE(mvm, "TE ended - current time %lu, estimated end %lu\n", iwl_time_now(mvm->dev),
+                 te_data->end_time);
 
-    switch (te_data->vif->type) {
+    switch (te_data->mvmvif->mac_role) {
+#if 0   // NEEDS_PORTING
       case NL80211_IFTYPE_P2P_DEVICE:
         ieee80211_remain_on_channel_expired(mvm->hw);
         set_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status);
         iwl_mvm_roc_finished(mvm);
         break;
-      case NL80211_IFTYPE_STATION:
+#endif  // NEEDS_PORTING
+      case WLAN_MAC_ROLE_CLIENT:
         /*
          * By now, we should have finished association
          * and know the dtim period.
          */
-        iwl_mvm_te_check_disconnect(mvm, te_data->vif,
+        iwl_mvm_te_check_disconnect(mvm, te_data->mvmvif,
                                     "No beacon heard and the time event is over already...");
         break;
       default:
@@ -314,8 +321,9 @@
     iwl_mvm_te_clear_data(mvm, te_data);
   } else if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_START) {
     te_data->running = true;
-    te_data->end_jiffies = TU_TO_EXP_TIME(te_data->duration);
+    te_data->end_time = TU_TO_DURATION(te_data->duration);
 
+#if 0   // NEEDS_PORTING
     if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) {
       set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status);
       iwl_mvm_ref(mvm, IWL_MVM_REF_ROC);
@@ -323,11 +331,13 @@
     } else if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) {
       iwl_mvm_te_handle_notify_csa(mvm, te_data, notif);
     }
+#endif  // NEEDS_PORTING
   } else {
     IWL_WARN(mvm, "Got TE with unknown action\n");
   }
 }
 
+#if 0   // NEEDS_PORTING
 /*
  * Handle A Aux ROC time event
  */
@@ -374,6 +384,7 @@
 
   return 0;
 }
+#endif  // NEEDS_PORTING
 
 /*
  * The Rx handler for time event notifications
@@ -386,21 +397,24 @@
   IWL_DEBUG_TE(mvm, "Time event notification - UID = 0x%x action %d\n",
                le32_to_cpu(notif->unique_id), le32_to_cpu(notif->action));
 
-  spin_lock_bh(&mvm->time_event_lock);
+  mtx_lock(&mvm->time_event_lock);
+
+#if 0   // NEEDS_PORTING
   /* This time event is triggered for Aux ROC request */
   if (!iwl_mvm_aux_roc_te_handle_notif(mvm, notif)) {
     goto unlock;
   }
+#endif  // NEEDS_PORTING
 
-  list_for_each_entry_safe(te_data, tmp, &mvm->time_event_list, list) {
+  list_for_every_entry_safe (&mvm->time_event_list, te_data, tmp, struct iwl_mvm_time_event_data,
+                             list) {
     if (le32_to_cpu(notif->unique_id) == te_data->uid) {
       iwl_mvm_te_handle_notif(mvm, te_data, notif);
     }
   }
-unlock:
-  spin_unlock_bh(&mvm->time_event_lock);
+
+  mtx_unlock(&mvm->time_event_lock);
 }
-#endif  // NEEDS_PORTING
 
 static bool iwl_mvm_te_notif(struct iwl_notif_wait_data* notif_wait, struct iwl_rx_packet* pkt,
                              void* data) {
@@ -481,6 +495,7 @@
     mtx_unlock(&mvm->time_event_lock);
     return ZX_ERR_BAD_STATE;
   }
+  // TODO(fxbug.dev/88102): ensure pointer validation.
   te_data->mvmvif = mvmvif;
   te_data->duration = le32_to_cpu(te_cmd->duration);
   te_data->id = le32_to_cpu(te_cmd->id);
diff --git a/third_party/iwlwifi/mvm/tx.c b/third_party/iwlwifi/mvm/tx.c
index 743a343..701ab44 100644
--- a/third_party/iwlwifi/mvm/tx.c
+++ b/third_party/iwlwifi/mvm/tx.c
@@ -38,7 +38,7 @@
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
 #include "third_party/iwlwifi/mvm/sta.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 #include "third_party/iwlwifi/platform/kernel.h"
 
 #if 0  // NEEDS_PORTING
@@ -270,8 +270,8 @@
 }
 
 #if 0  // NEEDS_PORTING
-static uint32_t iwl_mvm_get_tx_ant(struct iwl_mvm* mvm, struct ieee80211_tx_info* info,
-                                   struct ieee80211_sta* sta, __le16 fc) {
+static uint32_t iwl_mvm_get_tx_ant(struct iwl_mvm* mvm) {
+    // TODO(fxbug.dev/91465): Configure the ANT bit.
     if (info->band == NL80211_BAND_2GHZ && !iwl_mvm_bt_coex_is_shared_ant_avail(mvm)) {
         return mvm->cfg->non_shared_ant << RATE_MCS_ANT_POS;
     }
@@ -285,8 +285,7 @@
     return BIT(mvm->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS;
 }
 
-static uint32_t iwl_mvm_get_tx_rate(struct iwl_mvm* mvm, struct ieee80211_tx_info* info,
-                                    struct ieee80211_sta* sta) {
+static uint32_t iwl_mvm_get_tx_rate(struct iwl_mvm* mvm) {
     int rate_idx;
     uint8_t rate_plcp;
     uint32_t rate_flags = 0;
@@ -327,61 +326,74 @@
                                             struct ieee80211_sta* sta, __le16 fc) {
     return iwl_mvm_get_tx_rate(mvm, info, sta) | iwl_mvm_get_tx_ant(mvm, info, sta, fc);
 }
+
 #endif  // NEEDS_PORTING
 
 /*
  * Sets the fields in the Tx cmd that are rate related
  */
-void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm* mvm, struct iwl_tx_cmd* tx_cmd) {
+void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm* mvm, struct iwl_tx_cmd* tx_cmd,
+                             const struct ieee80211_frame_header* hdr) {
   /* Set retry limit on RTS packets */
   tx_cmd->rts_retry_limit = IWL_RTS_DFAULT_RETRY_LIMIT;
 
+  /* Set retry limit on DATA packets and Probe Responses*/
+  tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY;
+
+#if 1  // NEEDS_PORTING
+  // Return in advance if it's a data frame(except EAPOL frame), the rate of data frame is
+  // controlled by LINK_QUALITY command.
+  if (hdr != NULL && ieee80211_is_data(hdr) &&
+      mvm->fw_id_to_mac_id[0]->sta_state >= IWL_STA_AUTHORIZED) {
+    tx_cmd->initial_rate_index = 0;
+    tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE);
+    return;
+  }
+
   tx_cmd->rate_n_flags = iwl_mvm_mac80211_idx_to_hwrate(IWL_FIRST_OFDM_RATE) |
                          (BIT(mvm->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS);
 
-  /* Set retry limit on DATA packets and Probe Responses*/
-  tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY;
+#else  // NEEDS_PORTING
   // TODO(51120): below code needs rewrite to support QoS.
-#if 0  // NEEDS_PORTING
-    /* Set retry limit on DATA packets and Probe Responses*/
-    if (ieee80211_is_probe_resp(fc)) {
-        tx_cmd->data_retry_limit = IWL_MGMT_DFAULT_RETRY_LIMIT;
-        tx_cmd->rts_retry_limit = min(tx_cmd->data_retry_limit, tx_cmd->rts_retry_limit);
-    } else if (ieee80211_is_back_req(fc)) {
-        tx_cmd->data_retry_limit = IWL_BAR_DFAULT_RETRY_LIMIT;
-    } else {
-        tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY;
-    }
+  /* Set retry limit on DATA packets and Probe Responses*/
+  if (ieee80211_is_probe_resp(fc)) {
+    tx_cmd->data_retry_limit = IWL_MGMT_DFAULT_RETRY_LIMIT;
+    tx_cmd->rts_retry_limit = min(tx_cmd->data_retry_limit, tx_cmd->rts_retry_limit);
+  } else if (ieee80211_is_back_req(fc)) {
+    tx_cmd->data_retry_limit = IWL_BAR_DFAULT_RETRY_LIMIT;
+  } else {
+    tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY;
+  }
 
-    /*
-     * for data packets, rate info comes from the table inside the fw. This
-     * table is controlled by LINK_QUALITY commands
-     */
+  /*
+   * for data packets, rate info comes from the table inside the fw. This
+   * table is controlled by LINK_QUALITY commands
+   */
 
 #ifndef CPTCFG_IWLWIFI_FORCE_OFDM_RATE
-    if (ieee80211_is_data(fc) && sta) {
-        struct iwl_mvm_sta* mvmsta = iwl_mvm_sta_from_mac80211(sta);
+  if (ieee80211_is_data(fc) && sta) {
+    struct iwl_mvm_sta* mvmsta = iwl_mvm_sta_from_mac80211(sta);
 
-        if (mvmsta->sta_state >= IEEE80211_STA_AUTHORIZED) {
-            tx_cmd->initial_rate_index = 0;
-            tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE);
-            return;
-        }
-    } else if (ieee80211_is_back_req(fc)) {
-        tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR);
+    if (mvmsta->sta_state >= IEEE80211_STA_AUTHORIZED) {
+      tx_cmd->initial_rate_index = 0;
+      tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE);
+      return;
     }
+  } else if (ieee80211_is_back_req(fc)) {
+    tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR);
+  }
 #else
-    if (ieee80211_is_back_req(fc)) {
-        tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR);
-    }
+  if (ieee80211_is_back_req(fc)) {
+    tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR);
+  }
 #endif
 
-    /* Set the rate in the TX cmd */
-    tx_cmd->rate_n_flags = cpu_to_le32(iwl_mvm_get_tx_rate_n_flags(mvm, info, sta, fc));
+  /* Set the rate in the TX cmd */
+  tx_cmd->rate_n_flags = cpu_to_le32(iwl_mvm_get_tx_rate_n_flags(mvm, info, sta, fc));
 #endif  // NEEDS_PORTING
 }
 
-static void iwl_mvm_set_tx_cmd_pn(struct iwl_mvm_sta_key_conf* keyconf, uint8_t* ccmp_hdr) {
+static void iwl_mvm_set_tx_cmd_pn(struct ieee80211_key_conf* keyconf, uint8_t* ccmp_hdr) {
   uint64_t pn = atomic64_inc_return(&keyconf->tx_pn);
   ccmp_hdr[0] = pn;
   ccmp_hdr[2] = 0;
@@ -396,22 +408,23 @@
 /*
  * Sets the fields in the Tx cmd that are crypto related
  */
-static zx_status_t iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm* mvm, struct iwl_mvm_sta_key_conf* key,
+static zx_status_t iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm* mvm, struct ieee80211_tx_info* info,
                                              struct iwl_tx_cmd* tx_cmd,
                                              struct ieee80211_mac_packet* pkt) {
-  switch (key->cipher_type) {
+  struct ieee80211_key_conf* key_conf = info->control.hw_key;
+  switch (key_conf->cipher) {
     case CIPHER_SUITE_TYPE_CCMP_128:
       // Insert the CCMP header into the headroom space.
       if (sizeof(pkt->headroom) - pkt->headroom_used_size < 8) {
         return ZX_ERR_NO_SPACE;
       }
-      iwl_mvm_set_tx_cmd_ccmp(key, tx_cmd);
-      iwl_mvm_set_tx_cmd_pn(key, pkt->headroom + pkt->headroom_used_size);
+      iwl_mvm_set_tx_cmd_ccmp(key_conf, tx_cmd);
+      iwl_mvm_set_tx_cmd_pn(key_conf, pkt->headroom + pkt->headroom_used_size);
       tx_cmd->len += 8;
       pkt->headroom_used_size += 8;
       break;
     default:
-        tx_cmd->sec_ctl |= TX_CMD_SEC_EXT;
+      tx_cmd->sec_ctl |= TX_CMD_SEC_EXT;
   }
   return ZX_OK;
 }
@@ -429,6 +442,7 @@
  *
  */
 static zx_status_t iwl_mvm_set_tx_params(struct iwl_mvm* mvm, struct ieee80211_mac_packet* pkt,
+                                         struct ieee80211_tx_info* info,
                                          const struct iwl_mvm_sta* mvmsta,
                                          struct iwl_device_cmd* dev_cmd) {
   zx_status_t ret = ZX_OK;
@@ -512,13 +526,14 @@
   tx_cmd = (struct iwl_tx_cmd*)dev_cmd->payload;
   iwl_mvm_set_tx_cmd(mvm, pkt, tx_cmd, sta_id);
 
-  if (mvmsta->key_conf) {
-    if ((ret = iwl_mvm_set_tx_cmd_crypto(mvm, mvmsta->key_conf, tx_cmd, pkt)) != ZX_OK) {
+  if (info->control.hw_key) {
+    if ((ret = iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, pkt)) != ZX_OK) {
       return ret;
     }
   }
 
-  iwl_mvm_set_tx_cmd_rate(mvm, tx_cmd);
+  // Set rate ralated fields in iwl_tx_cmd
+  iwl_mvm_set_tx_cmd_rate(mvm, tx_cmd, pkt->common_header);
 
   /* Copy MAC header from pkt into command buffer */
   memcpy(tx_cmd->hdr, pkt->common_header, pkt->header_size);
@@ -950,14 +965,14 @@
   return ZX_OK;
 }
 
-zx_status_t iwl_mvm_tx_mpdu(struct iwl_mvm* mvm, struct ieee80211_mac_packet* pkt,
-                            struct iwl_mvm_sta* mvmsta) {
+static zx_status_t iwl_mvm_tx_mpdu(struct iwl_mvm* mvm, struct ieee80211_mac_packet* pkt,
+                                   struct ieee80211_tx_info* info, struct iwl_mvm_sta* mvmsta) {
   zx_status_t ret = ZX_OK;
   uint8_t tid = IWL_MAX_TID_COUNT;  // TODO(51120): support QoS
   uint16_t txq_id = mvmsta->tid_data[tid].txq_id;
 
   struct iwl_device_cmd dev_cmd;
-  if ((ret = iwl_mvm_set_tx_params(mvm, pkt, mvmsta, &dev_cmd)) != ZX_OK) {
+  if ((ret = iwl_mvm_set_tx_params(mvm, pkt, info, mvmsta, &dev_cmd)) != ZX_OK) {
     return ret;
   }
 
@@ -1072,6 +1087,7 @@
 
 zx_status_t iwl_mvm_tx_skb(struct iwl_mvm* mvm, struct ieee80211_mac_packet* pkt,
                            struct iwl_mvm_sta* mvmsta) {
+  struct ieee80211_tx_info info = pkt->info;
   if (!mvmsta) {
     IWL_ERR(mvm, "iwl_mvm_tx_skb(): mvmsta is NULL\n");
     return ZX_ERR_INVALID_ARGS;
@@ -1082,7 +1098,7 @@
     return ZX_ERR_INVALID_ARGS;
   }
 
-  return iwl_mvm_tx_mpdu(mvm, pkt, mvmsta);
+  return iwl_mvm_tx_mpdu(mvm, pkt, &info, mvmsta);
 
 #if 0   // NEEDS_PORTING
     // TODO(fxbug.dev/61069): supports TSO (TCP Segment Offload)/
diff --git a/third_party/iwlwifi/mvm/utils.c b/third_party/iwlwifi/mvm/utils.c
index 0feff42..81903fd 100644
--- a/third_party/iwlwifi/mvm/utils.c
+++ b/third_party/iwlwifi/mvm/utils.c
@@ -67,7 +67,6 @@
       iwl_mvm_ref(mvm, IWL_MVM_REF_SENDING_CMD);
     }
   }
-
   ret = iwl_trans_send_cmd(mvm->trans, cmd);
 
   if (!(cmd->flags & (CMD_ASYNC | CMD_SEND_IN_IDLE))) {
@@ -223,9 +222,9 @@
 zx_status_t mac80211_idx_to_data_rate(wlan_info_band_t band, int mac_idx, uint32_t* data_rate) {
   int band_offset;
 
-  if (band == WLAN_INFO_BAND_2GHZ) {
+  if (band == WLAN_INFO_BAND_TWO_GHZ) {
     band_offset = 0;
-  } else if (band == WLAN_INFO_BAND_5GHZ) {
+  } else if (band == WLAN_INFO_BAND_FIVE_GHZ) {
     band_offset = IWL_FIRST_OFDM_RATE;
   } else {
     return ZX_ERR_NOT_SUPPORTED;
@@ -284,7 +283,7 @@
 #endif  // NEEDS_PORTING
 
   /* Legacy rate format, search for match in table */
-  if (band == WLAN_INFO_BAND_5GHZ) {
+  if (band == WLAN_INFO_BAND_FIVE_GHZ) {
     band_offset = IWL_FIRST_OFDM_RATE;
   }
   for (int chan_idx = band_offset; chan_idx < IWL_RATE_COUNT_LEGACY; chan_idx++) {
@@ -705,27 +704,24 @@
  * progress.
  */
 zx_status_t iwl_mvm_send_lq_cmd(struct iwl_mvm* mvm, struct iwl_lq_cmd* lq, bool sync) {
-  return ZX_ERR_NOT_SUPPORTED;
-#if 0   // NEEDS_PORTING
-    struct iwl_host_cmd cmd = {
-        .id = LQ_CMD,
-        .len =
-            {
-                sizeof(struct iwl_lq_cmd),
-            },
-        .flags = sync ? 0 : CMD_ASYNC,
-        .data =
-            {
-                lq,
-            },
-    };
+  struct iwl_host_cmd cmd = {
+      .id = LQ_CMD,
+      .len =
+          {
+              sizeof(struct iwl_lq_cmd),
+          },
+      .flags = sync ? 0 : CMD_ASYNC,
+      .data =
+          {
+              lq,
+          },
+  };
 
-    if (WARN_ON(lq->sta_id == IWL_MVM_INVALID_STA || iwl_mvm_has_tlc_offload(mvm))) {
-        return -EINVAL;
-    }
-
-    return iwl_mvm_send_cmd(mvm, &cmd);
-#endif  // NEEDS_PORTING
+  if (WARN_ON(lq->sta_id == IWL_MVM_INVALID_STA || iwl_mvm_has_tlc_offload(mvm))) {
+    return ZX_ERR_INTERNAL;
+  }
+  IWL_DEBUG_RATE(NULL, "Sending LQ_CMD data...");
+  return iwl_mvm_send_cmd(mvm, &cmd);
 }
 
 /**
diff --git a/third_party/iwlwifi/pcie/BUILD.bazel b/third_party/iwlwifi/pcie/BUILD.bazel
index c0fff06..96e1d9e 100644
--- a/third_party/iwlwifi/pcie/BUILD.bazel
+++ b/third_party/iwlwifi/pcie/BUILD.bazel
@@ -6,7 +6,6 @@
 
 cc_library(
   name = "pcie",
-
   srcs = [
     "drv.c",
     "internal.h",
@@ -17,23 +16,9 @@
   ],
   hdrs = [ "entry.h" ],
   deps = [
- #   "//garnet/lib/wlan/protocol:protocol",
- #   "//sdk/banjo/fuchsia.hardware.pci:fuchsia.hardware.pci_banjo_c",
     "//third_party/iwlwifi:core",
     "//third_party/iwlwifi/fw",
- #   "//third_party/devices/pci/lib/device-protocol-pci",
- #   "//third_party/lib/ddk",
- #   "//zircon/system/ulib/backtrace-request",
- #   "//zircon/system/ulib/sync",
- #   "//zircon/system/ulib/zircon-internal",
   ],
- # public_deps = [
- #   "//third_party/iwlwifi/platform",
- #   "//zircon/system/public",
- # ]
- # friend =
+ #friend =
  #     [ "//third_party/iwlwifi/test:*" ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  #configs += [ "//build/config:Wno-conversion" ]
 )
diff --git a/third_party/iwlwifi/pcie/BUILD.gn b/third_party/iwlwifi/pcie/BUILD.gn
index e908134..b9d59e4 100644
--- a/third_party/iwlwifi/pcie/BUILD.gn
+++ b/third_party/iwlwifi/pcie/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-visibility = [ "//src/iwlwifi/*" ]
+visibility = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/*" ]
 
 source_set("pcie") {
   sources = [
@@ -15,22 +15,20 @@
   ]
   public = [ "entry.h" ]
   deps = [
-    "//garnet/lib/wlan/protocol:protocol",
     "//sdk/banjo/fuchsia.hardware.pci:fuchsia.hardware.pci_banjo_c",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/fw",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw",
     "//src/devices/pci/lib/device-protocol-pci",
     "//src/lib/ddk",
     "//zircon/system/ulib/backtrace-request",
     "//zircon/system/ulib/sync",
-    "//zircon/system/ulib/zircon-internal",
   ]
   public_deps = [
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//zircon/system/public",
   ]
   friend =
-      [ "//src/iwlwifi/test:*" ]
+      [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/test:*" ]
 
   # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
   configs += [ "//build/config:Wno-conversion" ]
diff --git a/third_party/iwlwifi/pcie/drv.c b/third_party/iwlwifi/pcie/drv.c
index 6ac8f46..0e1cb9d 100644
--- a/third_party/iwlwifi/pcie/drv.c
+++ b/third_party/iwlwifi/pcie/drv.c
@@ -34,9 +34,6 @@
  *
  *****************************************************************************/
 
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
 #include <stdlib.h>
 #include <zircon/status.h>
 
diff --git a/third_party/iwlwifi/pcie/internal.h b/third_party/iwlwifi/pcie/internal.h
index fb3fd80..9e3b853 100644
--- a/third_party/iwlwifi/pcie/internal.h
+++ b/third_party/iwlwifi/pcie/internal.h
@@ -50,7 +50,7 @@
 #include "third_party/iwlwifi/iwl-op-mode.h"
 #include "third_party/iwlwifi/iwl-trans.h"
 #include "third_party/iwlwifi/platform/compiler.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 #include "third_party/iwlwifi/platform/irq.h"
 #include "third_party/iwlwifi/platform/kernel.h"
 #include "third_party/iwlwifi/platform/memory.h"
diff --git a/third_party/iwlwifi/pcie/rx.c b/third_party/iwlwifi/pcie/rx.c
index 8a014fc..276612e 100644
--- a/third_party/iwlwifi/pcie/rx.c
+++ b/third_party/iwlwifi/pcie/rx.c
@@ -39,13 +39,13 @@
 #include <zircon/time.h>
 
 #if 0  // NEEDS_PORTING
-#include "iwl-context-info-gen3.h"
+#include "third_party/iwlwifi/iwl-context-info-gen3.h"
 #endif
-#include "third_party/iwlwifi/platform/align.h"
 #include "third_party/iwlwifi/iwl-io.h"
 #include "third_party/iwlwifi/iwl-op-mode.h"
 #include "third_party/iwlwifi/iwl-prph.h"
 #include "third_party/iwlwifi/pcie/internal.h"
+#include "third_party/iwlwifi/platform/align.h"
 #include "third_party/iwlwifi/platform/irq.h"
 #include "third_party/iwlwifi/platform/memory.h"
 
diff --git a/third_party/iwlwifi/pcie/trans.c b/third_party/iwlwifi/pcie/trans.c
index c7d1497..9a822b1 100644
--- a/third_party/iwlwifi/pcie/trans.c
+++ b/third_party/iwlwifi/pcie/trans.c
@@ -34,10 +34,6 @@
  *
  *****************************************************************************/
 #define _ALL_SOURCE  // for threads.h
-
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
 #include <lib/async/time.h>
 //#include <lib/device-protocol/pci.h>
 #include <stdint.h>
@@ -3265,9 +3261,9 @@
     }
 #endif  // NEEDS_PORTING
 
-// TODO(rsakthi) - unable to import device-protocol/pci.h
-//  status = pci_map_bar_buffer(trans_pcie->pci, 0 /* bar_id */, ZX_CACHE_POLICY_UNCACHED_DEVICE,
-//                              &trans_pcie->mmio);
+  // TODO(rsakthi) - unable to import device-protocol/pci.h
+  //status = pci_map_bar_buffer(trans_pcie->pci, 0 /* bar_id */, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+  //                            &trans_pcie->mmio);
   if (status != ZX_OK) {
     IWL_ERR(trans, "Failed to map resources for BAR 0: %s\n", zx_status_get_string(status));
     goto out_no_pci;
diff --git a/third_party/iwlwifi/pcie/tx.c b/third_party/iwlwifi/pcie/tx.c
index 72c1dca..ff48d09 100644
--- a/third_party/iwlwifi/pcie/tx.c
+++ b/third_party/iwlwifi/pcie/tx.c
@@ -39,7 +39,7 @@
 #include <zircon/status.h>
 #include <zircon/types.h>
 
-//#include <src/iwlwifi/ieee80211.h>
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 #if 0  // NEEDS_PORTING
 #include "third_party/iwlwifi/iwl-op-mode.h"
@@ -50,7 +50,7 @@
 #include "third_party/iwlwifi/iwl-prph.h"
 #include "third_party/iwlwifi/iwl-scd.h"
 #include "third_party/iwlwifi/pcie/internal.h"
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 #define IWL_TX_CRC_SIZE 4
 #define IWL_TX_DELIMITER_SIZE 4
diff --git a/third_party/iwlwifi/platform/BUILD.bazel b/third_party/iwlwifi/platform/BUILD.bazel
index bffbd52..d0bd06a 100644
--- a/third_party/iwlwifi/platform/BUILD.bazel
+++ b/third_party/iwlwifi/platform/BUILD.bazel
@@ -1,4 +1,8 @@
 
+# Copyright 2021 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
 package(default_visibility = ["//visibility:public"])
 
 load("@fuchsia_sdk//build_defs:driver_bind_rules.bzl", "driver_header_bind_rules", "driver_bytecode_bind_rules")
@@ -35,12 +39,14 @@
 cc_library(
     name = "platform",
     srcs = [
+        "compiler.cc",
         "debug.cc",
         "device.cc",
         "ieee80211.cc",
         "irq.cc",
         "memory.cc",
         "module.cc",
+        "rcu.cc",
         "task-internal.cc",
         "task.cc",
         "time.cc",
@@ -58,6 +64,7 @@
         "memory.h",
         "module.h",
         "pci.h",
+        "rcu.h",
         "task-internal.h",
         "task.h",
         "time.h",
@@ -69,30 +76,46 @@
         ],
     deps = [
         ":driver_inspector",
+        ":rcu_manager",
         "@fuchsia_sdk//pkg/ddk",
         "@fuchsia_sdk//fidl/fuchsia_hardware_pci:fuchsia_hardware_pci_banjo_cc",
-        "@fuchsia_sdk//fidl/fuchsia_hardware_wlanphyinfo:fuchsia_hardware_wlanphyinfo_banjo_cc",
-        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_mac:fuchsia_hardware_wlan_mac_banjo_cc",
+        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_phyinfo:fuchsia_hardware_wlan_phyinfo_banjo_cc",
+        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_softmac:fuchsia_hardware_wlan_softmac_banjo_cc",
 
         # Needed only for library purposes (channel and ieee includes)
         "@fuchsia_sdk//fidl/fuchsia_wlan_common:fuchsia_wlan_common_cc",
-        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_info:fuchsia_hardware_wlan_info_banjo_cc",
+        "@fuchsia_sdk//fidl/fuchsia_wlan_mlme:fuchsia_wlan_mlme_cc",
+        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_associnfo:fuchsia_hardware_wlan_associnfo_banjo_cc",
         ],
 )
 
+# Support for RCU synchronization.
+cc_library(
+    name = "rcu_manager",
+    srcs = [ "rcu-manager.cc" ],
+    hdrs = [ "rcu-manager.h" ],
+    deps = [
+        "@fuchsia_sdk//pkg/async",
+        "@fuchsia_sdk//pkg/async_cpp",
+    ],
+)
+
 cc_library(
     name = "fuchsia_device",
     srcs = [
     #    "bind.cc",
         "mvm-mlme.cc",
+        "mvm-sta.cc",
         "pcie-device.cc",
-        "wlanmac-device.cc",
+        "wlan-softmac-device.cc",
         "wlanphy-impl-device.cc",
         ],
     hdrs = [
         "mvm-mlme.h",
+        "mvm-sta.h",
         "pcie-device.h",
-        "wlanmac-device.h",
+        "scoped_utils.h",
+        "wlan-softmac-device.h",
         "wlanphy-impl-device.h",
         "ieee80211.h",
     ],
@@ -100,35 +123,27 @@
         ":driver_inspector",
     #   ":fuchsia_bind",
         ":platform",
+        ":rcu_manager",
         "@fuchsia_sdk//pkg/ddktl_experimental_driver_only",
-        "@fuchsia_sdk//fidl/fuchsia_hardware_wlanphyinfo:fuchsia_hardware_wlanphyinfo_banjo_cc",
-        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_mac:fuchsia_hardware_wlan_mac_banjo_cc",
+        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_phyinfo:fuchsia_hardware_wlan_phyinfo_banjo_cc",
+        "@fuchsia_sdk//fidl/fuchsia_hardware_wlan_softmac:fuchsia_hardware_wlan_softmac_banjo_cc",
         "@fuchsia_sdk//fidl/fuchsia_hardware_wlanphyimpl:fuchsia_hardware_wlanphyimpl_banjo_cc",
         "@fuchsia_sdk//fidl/fuchsia_wlan_common:fuchsia_wlan_common_banjo_cc",
         "@fuchsia_sdk//fidl/fuchsia_wlan_ieee80211:fuchsia_wlan_ieee80211_cc",
         "@fuchsia_sdk//fidl/fuchsia_wlan_internal:fuchsia_wlan_internal_banjo_cc",
-       "//third_party/iwlwifi:core",
-    #   "//third_party/iwlwifi/cfg",
-       "//third_party/iwlwifi/mvm:mvm",
-       "//third_party/iwlwifi/pcie",
-    #   "//third_party/devices/lib/driver",
+        "//third_party/iwlwifi:core",
+        "//third_party/iwlwifi/mvm:mvm",
+        "//third_party/iwlwifi/pcie",
         "@fuchsia_sdk//pkg/ddk",
         "@fuchsia_sdk//pkg/async_loop_cpp",
         "@fuchsia_sdk//pkg/async_loop_default",
-    #  "//third_party/lib/ddktl",
-    
-    # Zircon deps dont have to be mentioned I think.
-    #   "//zircon/system/public",
-    #   "//zircon/system/ulib/async-loop:async-loop-cpp",
-    #   "//zircon/system/ulib/async-loop:async-loop-default",
-    #   "//zircon/system/ulib/fbl",
     ],
-    copts = ["-Ithird_party/iwlwifi"],
-)
 
-#source_set("fuchsia_device") {
-#  friend =
-#      [ "//third_party/iwlwifi/test:*" ]
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-#  configs += [ "//build/config:Wno-conversion" ]
-#}
+    copts = ["-Ithird_party/iwlwifi"],
+
+    # This doesn't seem to be taking effect.
+    defines = [
+        "CPTCFG_IWL_TIMEOUT_FACTOR=1",
+        "CPTCFG_IWLMVM=1",
+    ],
+)
diff --git a/third_party/iwlwifi/platform/BUILD.gn b/third_party/iwlwifi/platform/BUILD.gn
index 13e83b7..a476f4b 100644
--- a/third_party/iwlwifi/platform/BUILD.gn
+++ b/third_party/iwlwifi/platform/BUILD.gn
@@ -4,19 +4,21 @@
 
 import("//build/bind/bind.gni")
 
-visibility = [ "//src/iwlwifi/*" ]
+visibility = [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/*" ]
 
 # This BUILD.gn defines the Fuchsia-specific platform support library for the iwlwifi driver.
 
 # Platform-support library for the iwlwifi driver.
 source_set("platform") {
   sources = [
+    "compiler.cc",
     "debug.cc",
     "device.cc",
     "ieee80211.cc",
     "irq.cc",
     "memory.cc",
     "module.cc",
+    "rcu.cc",
     "task-internal.cc",
     "task.cc",
     "time.cc",
@@ -31,27 +33,29 @@
     "memory.h",
     "module.h",
     "pci.h",
+    "rcu.h",
     "task-internal.h",
     "task.h",
     "time.h",
   ]
   deps = [
     ":driver_inspector",
+    ":rcu_manager",
     "//sdk/lib/stdcompat",
-    "//src/connectivity/wlan/lib/common/cpp:common",
-    "//zircon/system/ulib/sync",
+    "//zircon/system/ulib/async:async-cpp",
   ]
   public_deps = [
-    "//garnet/lib/wlan/protocol:protocol",
-    "//sdk/banjo/ddk.hw.wlan.wlaninfo:ddk.hw.wlan.wlaninfo_banjo_c",
     "//sdk/banjo/fuchsia.hardware.pci:fuchsia.hardware.pci_banjo_c",
-    "//sdk/banjo/fuchsia.hardware.wlan.mac:fuchsia.hardware.wlan.mac_banjo_c",
+    "//sdk/banjo/fuchsia.hardware.wlan.phyinfo:fuchsia.hardware.wlan.phyinfo_banjo_c",
+    "//sdk/banjo/fuchsia.hardware.wlan.softmac:fuchsia.hardware.wlan.softmac_banjo_c",
+    "//sdk/fidl/fuchsia.wlan.common:fuchsia.wlan.common_banjo_c",
+    "//src/connectivity/wlan/lib/common/cpp:common",
     "//src/lib/ddk",
     "//zircon/system/public",
     "//zircon/system/ulib/async",
   ]
   public_configs = [
-    "//src/iwlwifi:fuchsia_config",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:fuchsia_config",
   ]
 }
 
@@ -66,6 +70,17 @@
   ]
 }
 
+# Support for RCU synchronization.
+source_set("rcu_manager") {
+  sources = [ "rcu-manager.cc" ]
+  public = [ "rcu-manager.h" ]
+  deps = [
+    "//zircon/system/public",
+    "//zircon/system/ulib/async:async-cpp",
+  ]
+  public_deps = [ "//zircon/system/ulib/async" ]
+}
+
 driver_bind_rules("fuchsia_bind") {
   rules = "iwlwifi.bind"
   header_output = "iwlwifi-bind.h"
@@ -79,10 +94,13 @@
     "bind.cc",
     "mvm-mlme.cc",
     "mvm-mlme.h",
+    "mvm-sta.cc",
+    "mvm-sta.h",
     "pcie-device.cc",
     "pcie-device.h",
-    "wlanmac-device.cc",
-    "wlanmac-device.h",
+    "scoped_utils.h",
+    "wlan-softmac-device.cc",
+    "wlan-softmac-device.h",
     "wlanphy-impl-device.cc",
     "wlanphy-impl-device.h",
   ]
@@ -90,26 +108,27 @@
     ":driver_inspector",
     ":fuchsia_bind",
     ":platform",
-    "//sdk/banjo/ddk.hw.wlan.wlaninfo:ddk.hw.wlan.wlaninfo_banjo_cpp",
-    "//sdk/banjo/fuchsia.hardware.wlan.mac:fuchsia.hardware.wlan.mac_banjo_cpp",
+    ":rcu_manager",
+    "//sdk/banjo/fuchsia.hardware.wlan.phyinfo:fuchsia.hardware.wlan.phyinfo_banjo_cpp",
+    "//sdk/banjo/fuchsia.hardware.wlan.softmac:fuchsia.hardware.wlan.softmac_banjo_cpp",
     "//sdk/banjo/fuchsia.hardware.wlanphyimpl:fuchsia.hardware.wlanphyimpl_banjo_cpp",
     "//sdk/fidl/fuchsia.wlan.common:fuchsia.wlan.common_banjo_cpp",
     "//sdk/fidl/fuchsia.wlan.ieee80211:fuchsia.wlan.ieee80211_llcpp",
     "//sdk/fidl/fuchsia.wlan.internal:fuchsia.wlan.internal_banjo_cpp",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/cfg",
-    "//src/iwlwifi/mvm",
-    "//src/iwlwifi/pcie",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/cfg",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/pcie",
+    "//src/connectivity/wlan/lib/common/cpp:common",
     "//src/devices/lib/driver",
     "//src/lib/ddk",
     "//src/lib/ddktl",
     "//zircon/system/public",
     "//zircon/system/ulib/async-loop:async-loop-cpp",
     "//zircon/system/ulib/async-loop:async-loop-default",
-    "//zircon/system/ulib/fbl",
   ]
   friend =
-      [ "//src/iwlwifi/test:*" ]
+      [ "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/test:*" ]
 
   # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
   configs += [ "//build/config:Wno-conversion" ]
diff --git a/third_party/iwlwifi/platform/channel.cc b/third_party/iwlwifi/platform/channel.cc
index c9c824e..f6d3fa8 100644
--- a/third_party/iwlwifi/platform/channel.cc
+++ b/third_party/iwlwifi/platform/channel.cc
@@ -283,14 +283,28 @@
   switch (phy) {
     case WLAN_INFO_PHY_TYPE_DSSS:
       return "802.11 DSSS";
-    case WLAN_INFO_PHY_TYPE_CCK:
+    case WLAN_INFO_PHY_TYPE_HR:
       return "802.11b CCK/DSSS";
-    case WLAN_INFO_PHY_TYPE_OFDM:  // and WLAN_INFO_PHY_TYPE_ERP
+    case WLAN_INFO_PHY_TYPE_OFDM:
       return "802.11a/g OFDM";
+    case WLAN_INFO_PHY_TYPE_ERP:
+      return "802.11g ERP";
     case WLAN_INFO_PHY_TYPE_HT:
       return "802.11n HT";
+    case WLAN_INFO_PHY_TYPE_DMG:
+      return "802.11ad DMG";
     case WLAN_INFO_PHY_TYPE_VHT:
       return "802.11ac VHT";
+    case WLAN_INFO_PHY_TYPE_TVHT:
+      return "802.11af TVHT";
+    case WLAN_INFO_PHY_TYPE_S1G:
+      return "802.11ah S1G";
+    case WLAN_INFO_PHY_TYPE_CDMG:
+      return "802.11aj CDMG";
+    case WLAN_INFO_PHY_TYPE_CMMG:
+      return "802.11aj CMMG";
+    case WLAN_INFO_PHY_TYPE_HE:
+      return "802.11ax HE";
     default:
       return "UNKNOWN_PHY";
   }
@@ -300,7 +314,7 @@
   // TODO(fxbug.dev/29293): Streamline the enum values
   switch (phy) {
     case wlan_common::PHY::HR:
-      return WLAN_INFO_PHY_TYPE_CCK;
+      return WLAN_INFO_PHY_TYPE_HR;
     case wlan_common::PHY::ERP:
       return WLAN_INFO_PHY_TYPE_OFDM;
     case wlan_common::PHY::HT:
@@ -308,18 +322,18 @@
     case wlan_common::PHY::VHT:
       return WLAN_INFO_PHY_TYPE_VHT;
     case wlan_common::PHY::HEW:
-      return WLAN_INFO_PHY_TYPE_HEW;
+      return WLAN_INFO_PHY_TYPE_HE;
     default:
       //errorf("Unknown phy value: %d\n", phy);
       ZX_DEBUG_ASSERT(false);
-      return WLAN_INFO_PHY_TYPE_HEW;
+      return WLAN_INFO_PHY_TYPE_DSSS;
   }
 }
 
 ::fuchsia::wlan::common::PHY ToFidl(wlan_info_phy_type_t phy) {
   // TODO(fxbug.dev/29293): Streamline the enum values
   switch (phy) {
-    case WLAN_INFO_PHY_TYPE_CCK:
+    case WLAN_INFO_PHY_TYPE_HR:
       return wlan_common::PHY::HR;
     case WLAN_INFO_PHY_TYPE_OFDM:
       return wlan_common::PHY::ERP;
@@ -327,12 +341,12 @@
       return wlan_common::PHY::HT;
     case WLAN_INFO_PHY_TYPE_VHT:
       return wlan_common::PHY::VHT;
-    case WLAN_INFO_PHY_TYPE_HEW:
+    case WLAN_INFO_PHY_TYPE_HE:
       return wlan_common::PHY::HEW;
     default:
       //errorf("Unknown phy value: %d\n", phy);
       ZX_DEBUG_ASSERT(false);
-      return wlan_common::PHY::HEW;
+      return wlan_common::PHY::HR;
   }
 }
 
diff --git a/third_party/iwlwifi/platform/channel.h b/third_party/iwlwifi/platform/channel.h
index 4c01703..8c3b98c 100644
--- a/third_party/iwlwifi/platform/channel.h
+++ b/third_party/iwlwifi/platform/channel.h
@@ -5,15 +5,14 @@
 #ifndef SRC_CONNECTIVITY_WLAN_LIB_COMMON_CPP_INCLUDE_WLAN_COMMON_CHANNEL_H_
 #define SRC_CONNECTIVITY_WLAN_LIB_COMMON_CPP_INCLUDE_WLAN_COMMON_CHANNEL_H_
 
-#include <fuchsia/hardware/wlan/info/c/banjo.h>
+#include <fuchsia/hardware/wlan/associnfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
 #include <fuchsia/wlan/common/c/banjo.h>
-#include <fuchsia/wlan/common/cpp/fidl.h>
+#include <fuchsia/wlan/mlme/cpp/fidl.h>
 
 #include <cstdint>
 #include <string>
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
-
 bool operator==(const wlan_channel_t& lhs, const wlan_channel_t& rhs);
 bool operator!=(const wlan_channel_t& lhs, const wlan_channel_t& rhs);
 
@@ -58,11 +57,11 @@
   // See IEEE Std 802.11-2016 19.3.15
 };
 
-wlan_channel_t FromFidl(const fuchsia::wlan::common::WlanChannel& fidl_channel);
-fuchsia::wlan::common::WlanChannel ToFidl(const wlan_channel_t& channel);
+wlan_channel_t FromFidl(const ::fuchsia::wlan::common::WlanChannel& fidl_channel);
+::fuchsia::wlan::common::WlanChannel ToFidl(const wlan_channel_t& channel);
 
-wlan_info_phy_type_t FromFidl(fuchsia::wlan::common::PHY phy);
-fuchsia::wlan::common::PHY ToFidl(wlan_info_phy_type_t phy);
+wlan_info_phy_type_t FromFidl(::fuchsia::wlan::common::PHY phy);
+::fuchsia::wlan::common::PHY ToFidl(wlan_info_phy_type_t phy);
 
 const char* CbwSuffix(channel_bandwidth_t cbw);
 const char* CbwStr(channel_bandwidth_t cbw);
diff --git a/third_party/iwlwifi/platform/compiler.cc b/third_party/iwlwifi/platform/compiler.cc
new file mode 100644
index 0000000..63d3d26
--- /dev/null
+++ b/third_party/iwlwifi/platform/compiler.cc
@@ -0,0 +1,85 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/platform/compiler.h"
+
+#define BITARR_TYPE_NUM_BITS (sizeof(uint64_t) * 8)
+
+// Find the first asserted LSB(assume the input is little-endian).
+//
+// Returns:
+//   [0, num_bits): found. The index of first asserted bit (the least significant one).
+//   num_bits: No asserted bit found in num_bits.
+//
+size_t find_first_bit(unsigned int* bits, const size_t num_bits) {
+  const size_t num_of_ints = DIV_ROUND_UP(num_bits, BITS_PER_INT);
+  size_t ret = num_bits;
+
+  for (size_t i = 0; i < num_of_ints; ++i) {
+    if (bits[i] == 0) {
+      continue;
+    }
+    ret = (i * BITS_PER_INT) + __builtin_ctz(bits[i]);
+    break;
+  }
+
+  return MIN(num_bits, ret);
+}
+
+// Find the last asserted MSB(assume the input is little-endian).
+//
+// Returns:
+//   [0, num_bits): found. The index of last asserted bit (the most significant one).
+//   num_bits: No asserted bit found in num_bits.
+//
+size_t find_last_bit(unsigned int* bits, const size_t num_bits) {
+  const size_t num_of_ints = DIV_ROUND_UP(num_bits, BITS_PER_INT);
+  const size_t size_inside_int = (num_bits - 1) % BITS_PER_INT + 1;
+  unsigned int mask =
+      size_inside_int == BITS_PER_INT ? ~0U : (unsigned int)(1 << size_inside_int) - 1;
+
+  size_t ret = num_bits;
+  for (int64_t i = (int64_t)(num_of_ints - 1); i >= 0; --i) {
+    if (bits[i] == 0) {
+      continue;
+    }
+    unsigned int val = mask & bits[i];
+
+    if (val == 0) {
+      continue;
+    }
+    ret = (i * BITS_PER_INT) + (BITS_PER_INT - 1 - __builtin_clz(val));
+    break;
+  }
+
+  return MIN(num_bits, ret);
+}
+
+// Find next bit which is set in an unsigned integer.
+//
+// Returns:
+//   [0, num_bits): found. The index of next bit which is set to 1 starts from bit_offset.
+//   num_bits: No asserted bit found in num_bits.
+//
+size_t find_next_bit(unsigned int* bitarr, size_t num_bits, size_t bit_offset) {
+  if (bit_offset >= num_bits) {
+    return num_bits;
+  }
+
+  size_t word_offset = bit_offset / BITARR_TYPE_NUM_BITS;
+  size_t offset_within_word = bit_offset % BITARR_TYPE_NUM_BITS;
+
+  size_t rest = bitarr[word_offset] & (~0ULL << offset_within_word);
+  if (rest != 0) {
+    int bit = __builtin_ffsll(rest) - 1;
+    return MIN(word_offset * BITARR_TYPE_NUM_BITS + bit, num_bits);
+  }
+
+  size_t skipped_bits = (word_offset + 1) * BITARR_TYPE_NUM_BITS;
+  if (skipped_bits >= num_bits) {
+    return num_bits;
+  }
+
+  return skipped_bits + find_first_bit(bitarr + word_offset + 1, num_bits - skipped_bits);
+}
diff --git a/third_party/iwlwifi/platform/compiler.h b/third_party/iwlwifi/platform/compiler.h
index 1a7a2f9..ed0a1dd 100644
--- a/third_party/iwlwifi/platform/compiler.h
+++ b/third_party/iwlwifi/platform/compiler.h
@@ -14,6 +14,8 @@
 #include <string.h>
 #include <zircon/compiler.h>
 
+#include "third_party/iwlwifi/platform/debug.h"
+
 typedef uint32_t __be32;
 typedef uint16_t __be16;
 typedef uint64_t __le64;
@@ -22,12 +24,21 @@
 typedef int8_t __s8;
 typedef uint8_t __u8;
 
+#define U08 uint8_t
+#define U16 uint16_t
+#define U32 uint32_t
+#define U64 uint64_t
+#define INLINE inline
+
 #if defined(__cplusplus)
 extern "C++" {
 #include <atomic>
 }  // extern "C++"
 #define _Atomic(T) std::atomic<T>
+using std::memory_order_acq_rel;
+using std::memory_order_acquire;
 using std::memory_order_relaxed;
+using std::memory_order_release;
 using std::memory_order_seq_cst;
 #else  // defined(__cplusplus)
 #include <stdatomic.h>
@@ -133,7 +144,7 @@
 static inline void __set_bit(unsigned long bit, volatile unsigned long* addr) {
   volatile unsigned long* p = addr + (bit / BITS_PER_LONG);
   const unsigned long mask = 1ul << (bit % BITS_PER_LONG);
-  *p |= mask;
+  *p = *p | mask;
 }
 
 // Atomic operations.  DANGER DANGER DANGER HERE BE DRAGONS
@@ -227,25 +238,24 @@
 #define max_t(type, a, b) MAX((type)(a), (type)(b))
 #define min_t(type, a, b) MIN((type)(a), (type)(b))
 
-// Find the first asserted LSB.
-//
-// Returns:
-//   [0, num_bits): found. The index of first asserted bit (the least significant one.
-//   num_bits: No asserted bit found in num_bits.
-//
-static inline size_t find_first_bit(unsigned* bits, const size_t num_bits) {
-  const size_t num_of_ints = DIV_ROUND_UP(num_bits, BITS_PER_INT);
-  size_t ret = num_bits;
+size_t find_first_bit(unsigned int* bits, const size_t num_bits);
 
-  for (size_t i = 0; i < num_of_ints; ++i) {
-    if (bits[i] == 0) {
-      continue;
-    }
-    ret = (i * BITS_PER_INT) + __builtin_ctz(bits[i]);
-    break;
-  }
+size_t find_last_bit(unsigned int* bits, const size_t num_bits);
 
-  return MIN(num_bits, ret);
+#define BITARR_TYPE_NUM_BITS (sizeof(uint64_t) * 8)
+
+size_t find_next_bit(unsigned int* bitarr, size_t num_bits, size_t bit_offset);
+
+#define for_each_set_bit(bit, bitarr, num_bits)                          \
+  for ((bit) = find_first_bit((bitarr), (num_bits)); (bit) < (num_bits); \
+       (bit) = find_next_bit((bitarr), (num_bits), (bit) + 1))
+
+// This function calculates the hamming weight of an 8-bit bitmap.
+static inline uint8_t hweight8(uint8_t bitmap) {
+  uint8_t hw = bitmap - ((bitmap >> 1) & 0x55);
+  hw = (hw & 0x33) + ((hw >> 2) & 0x33);
+  hw = (hw + (hw >> 4)) & 0x0F;
+  return hw;
 }
 
 #if defined(__cplusplus)
diff --git a/third_party/iwlwifi/platform/debug.cc b/third_party/iwlwifi/platform/debug.cc
index eba96ea..ab02bae 100644
--- a/third_party/iwlwifi/platform/debug.cc
+++ b/third_party/iwlwifi/platform/debug.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "debug.h"
+#include "third_party/iwlwifi/platform/debug.h"
 
 #include <stdint.h>
 #include <stdio.h>
@@ -13,8 +13,8 @@
 #include <memory>
 #include <numeric>
 
-#include "driver-inspector.h"
-#include "kernel.h"
+#include "third_party/iwlwifi/platform/driver-inspector.h"
+#include "third_party/iwlwifi/platform/kernel.h"
 
 // Maximum bytes to dump in hex_dump_str()
 constexpr size_t kMaxDumpLenInARow = 16;
diff --git a/third_party/iwlwifi/platform/device.cc b/third_party/iwlwifi/platform/device.cc
index 54fd7d1..dca48bb 100644
--- a/third_party/iwlwifi/platform/device.cc
+++ b/third_party/iwlwifi/platform/device.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "device.h"
+#include "third_party/iwlwifi/platform/device.h"
 
 #include <lib/ddk/driver.h>
 
diff --git a/third_party/iwlwifi/platform/device.h b/third_party/iwlwifi/platform/device.h
index 34914cf..98b9a71 100644
--- a/third_party/iwlwifi/platform/device.h
+++ b/third_party/iwlwifi/platform/device.h
@@ -5,7 +5,7 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_DEVICE_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_DEVICE_H_
 
-#include "kernel.h"
+#include "third_party/iwlwifi/platform/kernel.h"
 
 #if defined(__cplusplus)
 extern "C" {
diff --git a/third_party/iwlwifi/platform/ieee80211.cc b/third_party/iwlwifi/platform/ieee80211.cc
index b891259..0f495f9 100644
--- a/third_party/iwlwifi/platform/ieee80211.cc
+++ b/third_party/iwlwifi/platform/ieee80211.cc
@@ -4,9 +4,10 @@
 
 // This file is in C++, as it interfaces with C++-only libraries.
 
-#include "ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
-#include "channel.h"
+#include "third_party/iwlwifi/platform/channel.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 size_t ieee80211_get_header_len(const struct ieee80211_frame_header* fw) {
   return ieee80211_hdrlen(fw);
diff --git a/third_party/iwlwifi/platform/ieee80211.h b/third_party/iwlwifi/platform/ieee80211.h
index 73aad70..fefd20a 100644
--- a/third_party/iwlwifi/platform/ieee80211.h
+++ b/third_party/iwlwifi/platform/ieee80211.h
@@ -19,13 +19,15 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_IEEE80211_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_IEEE80211_H_
 
-#include <fuchsia/hardware/wlan/mac/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
+#include <fuchsia/hardware/wlan/softmac/c/banjo.h>
 #include <netinet/if_ether.h>
 #include <stddef.h>
 #include <stdint.h>
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
-#include "ieee80211_include.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+
+#include "third_party/iwlwifi/platform/compiler.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -45,6 +47,34 @@
 #define IEEE80211_SCTL_SEQ_OFFSET 4
 #define IEEE80211_SEQ_TO_SN(seq) (((seq) >> IEEE80211_SCTL_SEQ_OFFSET) & IEEE80211_SCTL_SEQ_MASK)
 
+/* 802.11n HT capabilities masks (for cap_info) */
+#define IEEE80211_HT_CAP_LDPC_CODING 0x0001
+#define IEEE80211_HT_CAP_SUP_WIDTH_20_40 0x0002
+#define IEEE80211_HT_CAP_SM_PS 0x000C
+#define IEEE80211_HT_CAP_SM_PS_SHIFT 2
+#define IEEE80211_HT_CAP_GRN_FLD 0x0010
+#define IEEE80211_HT_CAP_SGI_20 0x0020
+#define IEEE80211_HT_CAP_SGI_40 0x0040
+#define IEEE80211_HT_CAP_TX_STBC 0x0080
+#define IEEE80211_HT_CAP_RX_STBC 0x0300
+#define IEEE80211_HT_CAP_RX_STBC_SHIFT 8
+#define IEEE80211_HT_CAP_DELAY_BA 0x0400
+#define IEEE80211_HT_CAP_MAX_AMSDU 0x0800
+#define IEEE80211_HT_CAP_DSSSCCK40 0x1000
+#define IEEE80211_HT_CAP_RESERVED 0x2000
+#define IEEE80211_HT_CAP_40MHZ_INTOLERANT 0x4000
+#define IEEE80211_HT_CAP_LSIG_TXOP_PROT 0x8000
+
+/* 802.11n HT capability MSC set */
+#define IEEE80211_HT_MCS_RX_HIGHEST_MASK 0x3ff
+#define IEEE80211_HT_MCS_TX_DEFINED 0x01
+#define IEEE80211_HT_MCS_TX_RX_DIFF 0x02
+
+#define IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK 0x0C
+#define IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT 2
+#define IEEE80211_HT_MCS_TX_MAX_STREAMS 4
+#define IEEE80211_HT_MCS_TX_UNEQUAL_MODULATION 0x10
+
 // The order of access categories is not clearly specified in 802.11-2016 Std.
 // Therefore it cannot be moved into ieee80211 banjo file.
 enum ieee80211_ac_numbers {
@@ -68,6 +98,18 @@
   IEEE80211_HT_MAX_AMPDU_64K = 3
 };
 
+/* Minimum MPDU start spacing */
+enum ieee80211_min_mpdu_spacing {
+  IEEE80211_HT_MPDU_DENSITY_NONE = 0, /* No restriction */
+  IEEE80211_HT_MPDU_DENSITY_0_25 = 1, /* 1/4 usec */
+  IEEE80211_HT_MPDU_DENSITY_0_5 = 2,  /* 1/2 usec */
+  IEEE80211_HT_MPDU_DENSITY_1 = 3,    /* 1 usec */
+  IEEE80211_HT_MPDU_DENSITY_2 = 4,    /* 2 usec */
+  IEEE80211_HT_MPDU_DENSITY_4 = 5,    /* 4 usec */
+  IEEE80211_HT_MPDU_DENSITY_8 = 6,    /* 8 usec */
+  IEEE80211_HT_MPDU_DENSITY_16 = 7    /* 16 usec */
+};
+
 enum ieee80211_roc_type {
   IEEE80211_ROC_TYPE_NORMAL = 0,
   IEEE80211_ROC_TYPE_MGMT_TX,
@@ -127,12 +169,28 @@
   int max_power;
 };
 
+struct ieee80211_mcs_info {
+  uint8_t rx_mask[IEEE80211_HT_MCS_MASK_LEN];
+  __le16 rx_highest_le;
+  uint8_t tx_params;
+  uint8_t reserved[3];
+} __packed;
+
+struct ieee80211_sta_ht_cap {
+  uint16_t cap; /* use IEEE80211_HT_CAP_ */
+  bool ht_supported;
+  uint8_t ampdu_factor;
+  uint8_t ampdu_density;
+  struct ieee80211_mcs_info mcs;
+};
+
 struct ieee80211_supported_band {
   wlan_info_band_t band;
   struct ieee80211_channel* channels;
   int n_channels;
   uint16_t* bitrates;
   int n_bitrates;
+  struct ieee80211_sta_ht_cap ht_cap;
 };
 
 struct ieee80211_tx_queue_params {
@@ -152,10 +210,6 @@
   struct ieee80211_txq* txq[IEEE80211_TIDS_MAX + 1];
 };
 
-struct ieee80211_tx_info {
-  void* driver_data[8];
-};
-
 struct ieee80211_txq {
   void* drv_priv;
 };
@@ -165,6 +219,28 @@
   uint8_t dummy;
 };
 
+/**
+ * struct ieee80211_key_conf - HW key configuration data
+ * @tx_pn - TX packet number, in host byte order
+ * @rx_seq - RX sequence number, in host byte order
+ */
+struct ieee80211_key_conf {
+  atomic64_t tx_pn;
+  uint64_t rx_seq;
+  uint32_t cipher;
+  uint8_t hw_key_idx;
+  uint8_t keyidx;
+  uint8_t key_type;
+  size_t keylen;
+  uint8_t key[0];
+};
+
+struct ieee80211_tx_info {
+  struct {
+    struct ieee80211_key_conf* hw_key;
+  } control;
+};
+
 // Struct for transferring an IEEE 802.11 MAC-framed packet around the driver.
 struct ieee80211_mac_packet {
   // The common portion of the MAC header.
@@ -185,6 +261,9 @@
 
   // MAC frame body size.
   size_t body_size;
+
+  // Control information for this packet.
+  struct ieee80211_tx_info info;
 };
 
 // Flags for the ieee80211_rx_status.flag
diff --git a/third_party/iwlwifi/platform/irq.cc b/third_party/iwlwifi/platform/irq.cc
index eba421e..236df2f 100644
--- a/third_party/iwlwifi/platform/irq.cc
+++ b/third_party/iwlwifi/platform/irq.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "irq.h"
+#include "third_party/iwlwifi/platform/irq.h"
 
 #include <lib/async/dispatcher.h>
 
 #include <memory>
 
-#include "task-internal.h"
+#include "third_party/iwlwifi/platform/task-internal.h"
 
 // Internally, the IRQ timers are just tasks, dispatched on the IRQ dispatcher.
 struct iwl_irq_timer : public wlan::iwlwifi::TaskInternal {
diff --git a/third_party/iwlwifi/platform/kernel.h b/third_party/iwlwifi/platform/kernel.h
index 70a8bf0..f869368 100644
--- a/third_party/iwlwifi/platform/kernel.h
+++ b/third_party/iwlwifi/platform/kernel.h
@@ -9,6 +9,8 @@
 // routines that are typically provided by the Linux kernel API.
 
 #include <fuchsia/hardware/pci/c/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/c/banjo.h>
+#include <fuchsia/wlan/common/c/banjo.h>
 #include <limits.h>
 #include <netinet/if_ether.h>
 #include <stdint.h>
@@ -17,8 +19,6 @@
 #include <zircon/assert.h>
 #include <zircon/listnode.h>
 
-#include <fuchsia/hardware/wlanphyinfo/c/banjo.h>
-
 #if defined(__cplusplus)
 extern "C" {
 #endif  // defined(__cplusplus)
@@ -35,21 +35,9 @@
 
 #define iwl_assert_lock_held(x) ZX_DEBUG_ASSERT(mtx_trylock(x) == thrd_busy)
 
-// NEEDS_TYPES: need protection while accessing the variable.
-#define rcu_dereference(p) (p)
-#define rcu_dereference_protected(p, c) (p)
-
 // NEEDS_TYPES: how to guarantee this?
 #define READ_ONCE(x) (x)
 
-// NEEDS_TYPES: implement this
-#define rcu_read_lock() \
-  do {                  \
-  } while (0);
-#define rcu_read_unlock() \
-  do {                    \
-  } while (0);
-
 #define DMA_BIT_MASK(n) (((n) >= 64) ? ~0ULL : ((1ULL << (n)) - 1))
 
 // Converts a WiFi time unit (1024 us) to zx_duration_t.
@@ -62,6 +50,7 @@
 struct zx_device;
 struct async_dispatcher;
 struct driver_inspector;
+struct rcu_manager;
 
 // This struct is analogous to the Linux device struct, and contains all the Fuchsia-specific data
 // fields relevant to generic device functionality.
@@ -83,6 +72,9 @@
   // we will maintain a dedicated IRQ dispatcher here.
   struct async_dispatcher* irq_dispatcher;
 
+  // The RCU manager instance used to manage RCU-based synchronization.
+  struct rcu_manager* rcu_manager;
+
   // The inspector used to publish the component inspection tree from the driver.
   struct driver_inspector* inspector;
 };
@@ -160,7 +152,7 @@
 };
 
 struct wireless_dev {
-  wlan_info_mac_role_t iftype;
+  wlan_mac_role_t iftype;
 };
 
 ////
diff --git a/third_party/iwlwifi/platform/memory.cc b/third_party/iwlwifi/platform/memory.cc
index 3bb4d73..49840e8 100644
--- a/third_party/iwlwifi/platform/memory.cc
+++ b/third_party/iwlwifi/platform/memory.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "memory.h"
+#include "third_party/iwlwifi/platform/memory.h"
 
 #include <lib/ddk/io-buffer.h>
 
diff --git a/third_party/iwlwifi/platform/memory.h b/third_party/iwlwifi/platform/memory.h
index 08ec6aa..3a9ee97 100644
--- a/third_party/iwlwifi/platform/memory.h
+++ b/third_party/iwlwifi/platform/memory.h
@@ -10,7 +10,7 @@
 #include <stdint.h>
 #include <zircon/types.h>
 
-#include "kernel.h"
+#include "third_party/iwlwifi/platform/kernel.h"
 
 #if defined(__cplusplus)
 extern "C" {
diff --git a/third_party/iwlwifi/platform/module.cc b/third_party/iwlwifi/platform/module.cc
index aca91a7..9171fc2 100644
--- a/third_party/iwlwifi/platform/module.cc
+++ b/third_party/iwlwifi/platform/module.cc
@@ -2,13 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
-#include "module.h"
+#include "third_party/iwlwifi/platform/module.h"
 
 #include <lib/ddk/driver.h>
-#include <lib/sync/completion.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <zircon/process.h>
@@ -18,7 +14,7 @@
 
 #include <string>
 
-#include "device.h"
+#include "third_party/iwlwifi/platform/device.h"
 
 static const size_t kModuleNameMax = 256;
 
diff --git a/third_party/iwlwifi/platform/mvm-mlme.cc b/third_party/iwlwifi/platform/mvm-mlme.cc
index 8a102e5..b795cab 100644
--- a/third_party/iwlwifi/platform/mvm-mlme.cc
+++ b/third_party/iwlwifi/platform/mvm-mlme.cc
@@ -46,11 +46,10 @@
 //   + Now, both sides of channel (SME and MLME) can talk now.
 //
 
-#include "mvm-mlme.h"
+#include "third_party/iwlwifi/platform/mvm-mlme.h"
 
-//#include <fidl/fuchsia.wlan.ieee80211/cpp/wire.h>
-#include <fuchsia/hardware/wlan/mac/c/banjo.h>
-#include <fuchsia/wlan/ieee80211/c/banjo.h>
+#include <fidl/fuchsia.wlan.ieee80211/cpp/wire.h>
+#include <fuchsia/hardware/wlan/softmac/c/banjo.h>
 #include <fuchsia/wlan/internal/c/banjo.h>
 #include <lib/ddk/device.h>
 #include <lib/ddk/driver.h>
@@ -59,23 +58,20 @@
 #include <zircon/status.h>
 
 #include <algorithm>
+#include <iterator>
 
-#include "ieee80211_include.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 extern "C" {
 #include "third_party/iwlwifi/iwl-debug.h"
 #include "third_party/iwlwifi/mvm/mvm.h"
+#include "third_party/iwlwifi/mvm/sta.h"
 #include "third_party/iwlwifi/mvm/time-event.h"
 }  // extern "C"
 
-#include "ieee80211.h"
-
-namespace {
-
-// IEEE 802.11-2016 3.2 (c.f. "vendor organizationally unique identifier")
-constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
-
-}  // namespace
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/rcu.h"
+#include "third_party/iwlwifi/platform/scoped_utils.h"
 
 ////////////////////////////////////  Helper Functions  ////////////////////////////////////////////
 
@@ -91,10 +87,10 @@
   size_t bands_count = 0;
 
   if (nvm_data->sku_cap_band_24ghz_enable) {
-    bands[bands_count++] = WLAN_INFO_BAND_2GHZ;
+    bands[bands_count++] = WLAN_INFO_BAND_TWO_GHZ;
   }
   if (nvm_data->sku_cap_band_52ghz_enable) {
-    bands[bands_count++] = WLAN_INFO_BAND_5GHZ;
+    bands[bands_count++] = WLAN_INFO_BAND_FIVE_GHZ;
   }
   ZX_ASSERT(bands_count <= WLAN_INFO_BAND_COUNT);
 
@@ -109,7 +105,7 @@
 //
 void fill_band_infos(const struct iwl_nvm_data* nvm_data, const wlan_info_band_t* bands,
                      size_t bands_count, wlan_info_band_info_t* band_infos) {
-  ZX_ASSERT(bands_count <= ARRAY_SIZE(nvm_data->bands));
+  ZX_ASSERT(bands_count <= std::size(nvm_data->bands));
 
   for (size_t band_idx = 0; band_idx < bands_count; ++band_idx) {
     wlan_info_band_t band_id = bands[band_idx];
@@ -117,16 +113,16 @@
     wlan_info_band_info_t* band_info = &band_infos[band_idx];                  // destination
 
     band_info->band = band_id;
-    band_info->ht_supported = nvm_data->sku_cap_11n_enable;
-    // TODO(43517): Better handling of driver features bits/flags
-    band_info->ht_caps.ht_capability_info =
-        IEEE80211_HT_CAPS_CHAN_WIDTH | IEEE80211_HT_CAPS_SMPS_DYNAMIC;
-    band_info->ht_caps.ampdu_params = (3 << IEEE80211_AMPDU_RX_LEN_SHIFT) |  // (64K - 1) bytes
-                                      (6 << IEEE80211_AMPDU_DENSITY_SHIFT);  // 8 us
-    // TODO(36683): band_info->ht_caps->supported_mcs_set =
+    band_info->ht_supported = sband->ht_cap.ht_supported;
+    band_info->ht_caps.ht_capability_info = sband->ht_cap.cap;
+    band_info->ht_caps.ampdu_params =
+        (sband->ht_cap.ampdu_factor << IEEE80211_AMPDU_RX_LEN_SHIFT) |   // (64K - 1) bytes
+        (sband->ht_cap.ampdu_density << IEEE80211_AMPDU_DENSITY_SHIFT);  // 8 us
+    memcpy(&band_info->ht_caps.supported_mcs_set, &sband->ht_cap.mcs,
+           sizeof(struct ieee80211_mcs_info));
     // TODO(36684): band_info->vht_caps =
 
-    ZX_ASSERT(sband->n_bitrates <= (int)ARRAY_SIZE(band_info->rates));
+    ZX_ASSERT(sband->n_bitrates <= static_cast<int>(std::size(band_info->rates)));
     for (int rate_idx = 0; rate_idx < sband->n_bitrates; ++rate_idx) {
       band_info->rates[rate_idx] = cfg_rates_to_80211(sband->bitrates[rate_idx]);
     }
@@ -134,62 +130,26 @@
     // Fill the channel list of this band.
     wlan_info_channel_list_t* ch_list = &band_info->supported_channels;
     switch (band_info->band) {
-      case WLAN_INFO_BAND_2GHZ:
+      case WLAN_INFO_BAND_TWO_GHZ:
         ch_list->base_freq = 2407;
         break;
-      case WLAN_INFO_BAND_5GHZ:
+      case WLAN_INFO_BAND_FIVE_GHZ:
         ch_list->base_freq = 5000;
         break;
       default:
         ZX_ASSERT(0);  // Unknown band ID.
         break;
     }
-    ZX_ASSERT(sband->n_channels <= (int)ARRAY_SIZE(ch_list->channels));
+    ZX_ASSERT(sband->n_channels <= static_cast<int>(std::size(ch_list->channels)));
     for (int ch_idx = 0; ch_idx < sband->n_channels; ++ch_idx) {
       ch_list->channels[ch_idx] = sband->channels[ch_idx].ch_num;
     }
   }
 }
 
-static struct iwl_mvm_sta* alloc_ap_mvm_sta(const uint8_t bssid[]) {
-  auto mvm_sta = reinterpret_cast<struct iwl_mvm_sta*>(calloc(1, sizeof(struct iwl_mvm_sta)));
-  if (!mvm_sta) {
-    return NULL;
-  }
-
-  for (size_t i = 0; i < ARRAY_SIZE(mvm_sta->txq); i++) {
-    mvm_sta->txq[i] = reinterpret_cast<struct iwl_mvm_txq*>(calloc(1, sizeof(struct iwl_mvm_txq)));
-  }
-  memcpy(mvm_sta->addr, bssid, ETH_ALEN);
-  mtx_init(&mvm_sta->lock, mtx_plain);
-
-  return mvm_sta;
-}
-
-static void free_ap_mvm_sta(struct iwl_mvm_sta* mvm_sta) {
-  if (!mvm_sta) {
-    return;
-  }
-
-  for (size_t i = 0; i < ARRAY_SIZE(mvm_sta->txq); i++) {
-    free(mvm_sta->txq[i]);
-  }
-  if (mvm_sta->key_conf) {
-    free(mvm_sta->key_conf);
-  }
-  free(mvm_sta);
-}
-
-static void reset_sta_mapping(struct iwl_mvm_vif* mvmvif) {
-  if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) {
-    mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id] = NULL;
-    mvmvif->ap_sta_id = IWL_MVM_INVALID_STA;
-  }
-}
-
 /////////////////////////////////////       MAC       //////////////////////////////////////////////
 
-zx_status_t mac_query(void* ctx, uint32_t options, wlanmac_info_t* info) {
+zx_status_t mac_query(void* ctx, uint32_t options, wlan_softmac_info_t* info) {
   const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
 
   if (!ctx || !info) {
@@ -204,9 +164,9 @@
   memcpy(info->sta_addr, nvm_data->hw_addr, sizeof(info->sta_addr));
   info->mac_role = mvmvif->mac_role;
   // TODO(43517): Better handling of driver features bits/flags
-  info->driver_features = WLAN_INFO_DRIVER_FEATURE_SCAN_OFFLOAD;
-  info->supported_phys = WLAN_INFO_PHY_TYPE_DSSS | WLAN_INFO_PHY_TYPE_CCK |
-                         WLAN_INFO_PHY_TYPE_OFDM | WLAN_INFO_PHY_TYPE_HT;
+  info->driver_features = WLAN_INFO_DRIVER_FEATURE_SCAN_OFFLOAD | WLAN_INFO_DRIVER_FEATURE_MFP;
+  info->supported_phys = WLAN_INFO_PHY_TYPE_DSSS | WLAN_INFO_PHY_TYPE_HR | WLAN_INFO_PHY_TYPE_OFDM |
+                         WLAN_INFO_PHY_TYPE_HT;
   info->caps = WLAN_INFO_HARDWARE_CAPABILITY_SHORT_PREAMBLE |
                WLAN_INFO_HARDWARE_CAPABILITY_SPECTRUM_MGMT |
                WLAN_INFO_HARDWARE_CAPABILITY_SHORT_SLOT_TIME;
@@ -220,7 +180,8 @@
   return ZX_OK;
 }
 
-zx_status_t mac_start(void* ctx, const wlanmac_ifc_protocol_t* ifc, zx_handle_t* out_mlme_channel) {
+zx_status_t mac_start(void* ctx, const wlan_softmac_ifc_protocol_t* ifc,
+                      zx_handle_t* out_mlme_channel) {
   const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
 
   if (!ctx || !ifc || !out_mlme_channel) {
@@ -252,39 +213,14 @@
   return ret;
 }
 
-void mac_stop(void* ctx) {
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
-  zx_status_t ret;
-
-  // Change the sta state linking to the AP.
-  if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) {
-    struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id];
-    if (!mvm_sta) {
-      IWL_ERR(mvmvif, "sta info is not set before stop.\n");
-    } else {
-      ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NONE, IWL_STA_NOTEXIST);
-      if (ret != ZX_OK) {
-        IWL_ERR(mvmvif, "Cannot set station state to NOT EXIST: %s\n", zx_status_get_string(ret));
-      }
-    }
-  }
+void mac_stop(struct iwl_mvm_vif* mvmvif) {
+  zx_status_t ret = ZX_OK;
 
   if (mvmvif->phy_ctxt) {
     ret = iwl_mvm_remove_chanctx(mvmvif->mvm, mvmvif->phy_ctxt->id);
     if (ret != ZX_OK) {
       IWL_WARN(mvmvif, "Cannot remove chanctx: %s\n", zx_status_get_string(ret));
     }
-  } else {
-    IWL_WARN(mvmvif, "PHY context is NULL in %s()\n", __func__);
-  }
-
-  // Clean up other sta info.
-  for (size_t i = 0; i < ARRAY_SIZE(mvmvif->mvm->fw_id_to_mac_id); i++) {
-    struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[i];
-    if (mvm_sta) {
-      free_ap_mvm_sta(mvm_sta);
-      mvmvif->mvm->fw_id_to_mac_id[i] = NULL;
-    }
   }
 
   ret = iwl_mvm_mac_remove_interface(mvmvif);
@@ -293,35 +229,6 @@
   }
 }
 
-zx_status_t mac_queue_tx(void* ctx, uint32_t options, const wlan_tx_packet_t* tx_packet) {
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
-
-  if (tx_packet->mac_frame_size > WLAN_MSDU_MAX_LEN) {
-    IWL_ERR(mvmvif, "Frame size is to large (%lu). expect less than %lu.\n",
-            tx_packet->mac_frame_size, WLAN_MSDU_MAX_LEN);
-    return ZX_ERR_INVALID_ARGS;
-  }
-
-  ieee80211_mac_packet packet = {};
-  packet.common_header =
-      reinterpret_cast<const ieee80211_frame_header*>(tx_packet->mac_frame_buffer);
-  packet.header_size = ieee80211_get_header_len(packet.common_header);
-  if (packet.header_size > tx_packet->mac_frame_size) {
-    IWL_ERR(mvmvif, "TX packet header size %zu too large for data size %zu\n", packet.header_size,
-            tx_packet->mac_frame_size);
-    return ZX_ERR_INVALID_ARGS;
-  }
-
-  packet.body = tx_packet->mac_frame_buffer + packet.header_size;
-  packet.body_size = tx_packet->mac_frame_size - packet.header_size;
-
-  mtx_lock(&mvmvif->mvm->mutex);
-  zx_status_t ret = iwl_mvm_mac_tx(mvmvif, &packet);
-  mtx_unlock(&mvmvif->mvm->mutex);
-
-  return ret;
-}
-
 // This function will ensure the mvmvif->phy_ctxt is valid (either get a free one from pool
 // or use the assigned one).
 //
@@ -340,12 +247,39 @@
   return ZX_OK;
 }
 
-static zx_status_t mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta);
+static zx_status_t remove_chanctx(struct iwl_mvm_vif* mvmvif) {
+  zx_status_t ret;
+
+  // mvmvif->phy_ctxt will be cleared up in iwl_mvm_unassign_vif_chanctx(). So back up the phy
+  // context ID and the chandef pointer for later use.
+  auto phy_ctxt_id = mvmvif->phy_ctxt->id;
+  auto chandef = &mvmvif->phy_ctxt->chandef;
+
+  // Unbinding MAC and PHY contexts.
+  ret = iwl_mvm_unassign_vif_chanctx(mvmvif);
+  if (ret != ZX_OK) {
+    IWL_ERR(mvmvif, "cannot unassign VIF channel context: %s\n", zx_status_get_string(ret));
+    goto out;
+  }
+
+  ret = iwl_mvm_remove_chanctx(mvmvif->mvm, phy_ctxt_id);
+  if (ret != ZX_OK) {
+    IWL_ERR(mvmvif, "Cannot remove channel context: %s\n", zx_status_get_string(ret));
+    goto out;
+  }
+
+  // Clear the chandef in mvm->phy_ctxts[] (was pointed by mvmvif->phy_ctxt->chandef) to indicate
+  // this phy_ctxt is unused.
+  memset(chandef, 0, sizeof(*chandef));
+
+out:
+  return ret;
+}
 
 // This is called right after SSID scan. The MLME tells this function the channel to tune in.
 // This function configures the PHY context and binds the MAC to that PHY context.
-zx_status_t mac_set_channel(void* ctx, uint32_t options, const wlan_channel_t* channel) {
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
+zx_status_t mac_set_channel(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                            const wlan_channel_t* channel) {
   zx_status_t ret;
 
   IWL_INFO(mvmvif, "mac_set_channel(primary:%d, bandwidth:'%s', secondary:%d)\n", channel->primary,
@@ -358,31 +292,14 @@
                                                           : "unknown",
            channel->secondary80);
 
-  if (mvmvif->phy_ctxt && mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) {
-    // We already have PHY context set. It probably was left from the last association attempt
-    // (which was rejected by the AP). Remove it first.
-    IWL_INFO(mvmvif, "We already have PHY context set (ap_sta_id=%d). Removing it.\n",
-             mvmvif->ap_sta_id);
-
-    // In the normal case, the time event is added in the mac_configure_bss() and removed in the
-    // mac_configure_assoc(). So in the abnormal cases (assoc failed), we need to remove it before
-    // configure_bss().
-    mtx_lock(&mvmvif->mvm->mutex);
-    ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
-    mtx_unlock(&mvmvif->mvm->mutex);
+  if (mvmvif->phy_ctxt && mvmvif->phy_ctxt->chandef.primary != 0) {
+    // The PHY context is set (the RF is on a particular channel). Remove it first. Below code
+    // will allocate a new one.
+    ret = remove_chanctx(mvmvif);
     if (ret != ZX_OK) {
-      IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
+      IWL_ERR(mvmvif, "Cannot reset PHY context: %s\n", zx_status_get_string(ret));
       return ret;
     }
-
-    auto mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id];
-    if (mvm_sta) {
-      ret = mac_unconfigure_bss(mvmvif, mvm_sta);
-      if (ret != ZX_OK) {
-        IWL_ERR(mvmvif, "Cannot unconfigure bss: %s\n", zx_status_get_string(ret));
-        return ret;
-      }
-    }
   }
 
   // Before we do anything, ensure the PHY context had been assigned to the mvmvif.
@@ -411,8 +328,8 @@
 }
 
 // This is called after mac_set_channel(). The MAC (mvmvif) will be configured as a CLIENT role.
-zx_status_t mac_configure_bss(void* ctx, uint32_t options, const bss_config_t* config) {
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
+zx_status_t mac_configure_bss(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                              const bss_config_t* config) {
   zx_status_t ret = ZX_OK;
 
   IWL_INFO(mvmvif, "mac_configure_bss(bssid=%02x:%02x:%02x:%02x:%02x:%02x, type=%d, remote=%d)\n",
@@ -424,61 +341,45 @@
     return ZX_ERR_INVALID_ARGS;
   }
 
-  if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) {
-    IWL_ERR(mvmvif, "The AP sta ID has been set already. ap_sta_id=%d\n", mvmvif->ap_sta_id);
-    return ZX_ERR_ALREADY_EXISTS;
-  }
-  // Note that 'ap_sta_id' is unset and later will be set in iwl_mvm_add_sta().
+  {
+    // Copy the BSSID info.
+    auto lock = std::lock_guard(mvmvif->mvm->mutex);
+    memcpy(mvmvif->bss_conf.bssid, config->bssid, ETH_ALEN);
+    memcpy(mvmvif->bssid, config->bssid, ETH_ALEN);
 
-  // Copy the BSSID info.
-  mtx_lock(&mvmvif->mvm->mutex);
-  memcpy(mvmvif->bss_conf.bssid, config->bssid, ETH_ALEN);
-  memcpy(mvmvif->bssid, config->bssid, ETH_ALEN);
-
-  // Simulates the behavior of iwl_mvm_bss_info_changed_station().
-  ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, mvmvif->bssid);
-  mtx_unlock(&mvmvif->mvm->mutex);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot set BSSID: %s\n", zx_status_get_string(ret));
-    return ret;
-  }
-
-  // Add AP into the STA table in the firmware.
-  struct iwl_mvm_sta* mvm_sta = alloc_ap_mvm_sta(config->bssid);
-  if (!mvm_sta) {
-    IWL_ERR(mvmvif, "cannot allocate MVM STA for AP.\n");
-    return ZX_ERR_NO_RESOURCES;
-  }
-
-  // mvm_sta ownership will be transfered to mvm->fw_id_to_mac_id[] in iwl_mvm_add_sta(). It will be
-  // freed at mac_clear_assoc().
-  // Below is to how the iwl_mvm_mac_sta_state() is called.
-  ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NOTEXIST, IWL_STA_NONE);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot set MVM STA state: %s\n", zx_status_get_string(ret));
-    goto exit;
+    // Simulates the behavior of iwl_mvm_bss_info_changed_station().
+    ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, mvmvif->bssid);
+    if (ret != ZX_OK) {
+      IWL_ERR(mvmvif, "cannot set BSSID: %s\n", zx_status_get_string(ret));
+      return ret;
+    }
   }
 
   // Ask the firmware to pay attention for beacon.
   // Note that this would add TIME_EVENT as well.
   iwl_mvm_mac_mgd_prepare_tx(mvmvif->mvm, mvmvif, IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS);
 
-  // Allocate a Tx queue for this station.
-  mtx_lock(&mvmvif->mvm->mutex);
-  ret = iwl_mvm_sta_alloc_queue(mvmvif->mvm, mvm_sta, IEEE80211_AC_BE, IWL_MAX_TID_COUNT);
-  mtx_unlock(&mvmvif->mvm->mutex);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot allocate queue for STA: %s\n", zx_status_get_string(ret));
-    goto exit;
+  return ZX_OK;
+}
+
+// This function is to revert what mac_configure_bss() does.
+zx_status_t mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif) {
+  zx_status_t ret = ZX_OK;
+
+  {
+    // To simulate the behavior that iwl_mvm_bss_info_changed_station() would do for disassocitaion.
+    auto lock = std::lock_guard(mvmvif->mvm->mutex);
+    memset(mvmvif->bss_conf.bssid, 0, ETH_ALEN);
+    memset(mvmvif->bssid, 0, ETH_ALEN);
+    // This will take the cleared BSSID from bss_conf and update the firmware.
+    ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
+    if (ret != ZX_OK) {
+      IWL_ERR(mvm, "failed to update MAC (clear after unassoc)\n");
+      return ret;
+    }
   }
 
-exit:
-  // If it is successful, the ownership has been transferred. If not, free the resource.
-  if (ret != ZX_OK) {
-    free_ap_mvm_sta(mvm_sta);
-    reset_sta_mapping(mvmvif);
-  }
-  return ret;
+  return ZX_OK;
 }
 
 zx_status_t mac_enable_beaconing(void* ctx, uint32_t options, const wlan_bcn_config_t* bcn_cfg) {
@@ -486,259 +387,127 @@
   return ZX_ERR_NOT_SUPPORTED;
 }
 
-zx_status_t mac_configure_beacon(void* ctx, uint32_t options, const wlan_tx_packet_t* pkt) {
+zx_status_t mac_configure_beacon(void* ctx, uint32_t options,
+                                 const wlan_tx_packet_t* packet_template) {
   IWL_ERR(ctx, "%s() needs porting ... see fxbug.dev/36742\n", __func__);
   return ZX_ERR_NOT_SUPPORTED;
 }
 
-zx_status_t mac_set_key(void* ctx, uint32_t options, const wlan_key_config_t* key_config) {
-  zx_status_t status = ZX_OK;
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
-  iwl_mvm* mvm = mvmvif->mvm;
-
-  if (mvm->trans->cfg->gen2 || iwl_mvm_has_new_tx_api(mvm)) {
-    // The new firmwares (for starting with the 22000 series) have different packet generation
-    // requirements than mentioned below.
-    return ZX_ERR_NOT_SUPPORTED;
-  }
-
-  if (!std::equal(key_config->cipher_oui,
-                  key_config->cipher_oui + std::size(key_config->cipher_oui), kIeeeOui,
-                  kIeeeOui + std::size(kIeeeOui))) {
-    // IEEE 802.11-2016 9.4.2.25.2
-    // The standard ciphers all live in the IEEE space.
-    return ZX_ERR_NOT_SUPPORTED;
-  }
-
-  switch (key_config->cipher_type) {
-    case CIPHER_SUITE_TYPE_CCMP_128:
-      // Note: the Linux iwlwifi driver requests IEEE80211_KEY_FLAG_PUT_IV_SPACE from the mac80211
-      // stack.  We will apply equivalent functionality manually to Incoming packets from Fuchsia.
-      break;
-    default:
-      // Additional porting required for other types.
-      return ZX_ERR_NOT_SUPPORTED;
-  }
-
-  auto key_conf = reinterpret_cast<iwl_mvm_sta_key_conf*>(
-      malloc(sizeof(iwl_mvm_sta_key_conf) + key_config->key_len));
-  memset(key_conf, 0, sizeof(*key_conf) + key_config->key_len);
-  key_conf->cipher_type = key_config->cipher_type;
-  key_conf->key_type = key_config->key_type;
-  key_conf->keyidx = key_config->key_idx;
-  key_conf->keylen = key_config->key_len;
-  key_conf->rx_seq = key_config->rsc;
-  memcpy(key_conf->key, key_config->key, key_conf->keylen);
-
-  struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id];
-  if ((status = iwl_mvm_mac_set_key(mvmvif, mvm_sta, key_conf)) != ZX_OK) {
-    free(key_conf);
-    IWL_ERR(mvmvif, "iwl_mvm_mac_set_key() failed: %s\n", zx_status_get_string(status));
-    return status;
-  }
-
-  if (key_conf->key_type == WLAN_KEY_TYPE_PAIRWISE) {
-    // Save the pairwise key, for use in the TX path.  Group keys are receive-only and do not need
-    // to be saved.
-    free(mvm_sta->key_conf);
-    mvm_sta->key_conf = key_conf;
-  } else {
-    free(key_conf);
-  }
-
-  return ZX_OK;
-}
-
 // Set the association result to the firmware.
 //
 // The current mac context is set by mac_configure_bss() with default values.
-//   TODO(fxbug.dev/36683): supports HT (802.11n)
 //   TODO(fxbug.dev/36684): supports VHT (802.11ac)
 //
-zx_status_t mac_configure_assoc(void* ctx, uint32_t options, const wlan_assoc_ctx_t* assoc_ctx) {
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
+zx_status_t mac_configure_assoc(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                                const wlan_assoc_ctx_t* assoc_ctx) {
   zx_status_t ret = ZX_OK;
-
   IWL_INFO(ctx, "Associating ...\n");
 
+  // TODO(fxbug.dev/86715): this RCU-unprotected access is safe as deletions from the map are
+  // RCU-synchronized from API calls to mac_stop() in this same thread.
   struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id];
   if (!mvm_sta) {
     IWL_ERR(mvmvif, "sta info is not set before association.\n");
     ret = ZX_ERR_BAD_STATE;
-    goto out;
+    return ret;
+  }
+
+  // Save band info into interface struct for future usage.
+  mvmvif->phy_ctxt->band = iwl_mvm_get_channel_band(assoc_ctx->channel.primary);
+
+  mvm_sta->bw = assoc_ctx->channel.cbw;
+  // Record the intersection of AP and station supported rate to mvm_sta.
+  ZX_ASSERT(assoc_ctx->rates_cnt <= sizeof(mvm_sta->supp_rates));
+  memcpy(mvm_sta->supp_rates, assoc_ctx->rates, assoc_ctx->rates_cnt);
+
+  // Copy HT related fields from wlan_assoc_ctx_t.
+  mvm_sta->support_ht = assoc_ctx->has_ht_cap;
+  if (assoc_ctx->has_ht_cap) {
+    memcpy(&mvm_sta->ht_cap, &assoc_ctx->ht_cap, sizeof(struct ieee80211_ht_capabilities));
   }
 
   // Change the station states step by step.
-
   ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NONE, IWL_STA_AUTH);
   if (ret != ZX_OK) {
     IWL_ERR(mvmvif, "cannot set state from NONE to AUTH: %s\n", zx_status_get_string(ret));
-    goto out;
+    return ret;
   }
 
   ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_AUTH, IWL_STA_ASSOC);
   if (ret != ZX_OK) {
     IWL_ERR(mvmvif, "cannot set state from AUTH to ASSOC: %s\n", zx_status_get_string(ret));
-    goto out;
+    return ret;
   }
   ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_ASSOC, IWL_STA_AUTHORIZED);
   if (ret != ZX_OK) {
     IWL_ERR(mvmvif, "cannot set state from ASSOC to AUTHORIZED: %s\n", zx_status_get_string(ret));
-    goto out;
+    return ret;
   }
 
   // Tell firmware to pass multicast packets to driver.
   iwl_mvm_configure_filter(mvmvif->mvm);
 
-  mtx_lock(&mvmvif->mvm->mutex);
+  {
+    auto lock = std::lock_guard(mvmvif->mvm->mutex);
 
-  // Update the MAC context in the firmware.
-  mvmvif->bss_conf.assoc = true;
-  mvmvif->bss_conf.listen_interval = assoc_ctx->listen_interval;
-  ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot update MAC context in the firmware: %s\n", zx_status_get_string(ret));
-    goto unlock;
+    // Update the MAC context in the firmware.
+    mvmvif->bss_conf.assoc = true;
+    mvmvif->bss_conf.listen_interval = assoc_ctx->listen_interval;
+    ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
+    if (ret != ZX_OK) {
+      IWL_ERR(mvmvif, "cannot update MAC context in the firmware: %s\n", zx_status_get_string(ret));
+      return ret;
+    }
+
+    ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
+    if (ret != ZX_OK) {
+      IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
+      return ret;
+    }
   }
 
-  ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
-    goto unlock;
-  }
+  // Tell firmware to pass multicast packets to driver.
+  iwl_mvm_configure_filter(mvmvif->mvm);
 
   // TODO(43218): support multiple interfaces. Need to port iwl_mvm_update_quotas() in mvm/quota.c.
   // TODO(56093): support low latency in struct iwl_time_quota_data.
-
-unlock:
-  mtx_unlock(&mvmvif->mvm->mutex);
-out:
-  return ret;
+  return ZX_OK;
 }
 
-zx_status_t mac_clear_assoc(void* ctx, uint32_t options,
-                            const uint8_t peer_addr[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]) {
+zx_status_t mac_clear_assoc(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                            const uint8_t peer_addr[fuchsia_wlan_ieee80211::wire::kMacAddrLen]) {
   IWL_INFO(ctx, "Disassociating ...\n");
 
-  const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
   zx_status_t ret = ZX_OK;
 
-  if (mvmvif->ap_sta_id == IWL_MVM_INVALID_STA) {
-    IWL_ERR(mvmif, "sta id has not been set yet");
-    return ZX_ERR_BAD_STATE;
-  }
-  // iwl_mvm_rm_sta() will reset the ap_sta_id value so that we have to keep it.
-  uint8_t ap_sta_id = mvmvif->ap_sta_id;
-
-  struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[ap_sta_id];
-  if (!mvm_sta) {
-    IWL_ERR(mvmvif, "sta info is not set before disassociation.\n");
-    return ZX_ERR_BAD_STATE;
+  {
+    auto lock = std::lock_guard(mvmvif->mvm->mutex);
+    // Remove Time event (in case assoc failed)
+    ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
+    if (ret != ZX_OK) {
+      IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
+    }
   }
 
-  // Mark the station is no longer associated. This must be set before iwl_mvm_mac_sta_state().
-  mvmvif->bss_conf.assoc = false;
-
-  // Below are to simulate the behavior of iwl_mvm_bss_info_changed_station().
-  ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_AUTHORIZED, IWL_STA_ASSOC);
+  ret = mac_unconfigure_bss(mvmvif);
   if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot set state from AUTHORIZED to ASSOC: %s\n", zx_status_get_string(ret));
-    goto out;
-  }
-  ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_ASSOC, IWL_STA_AUTH);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot set state from ASSOC to AUTH: %s\n", zx_status_get_string(ret));
-    goto out;
-  }
-  ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_AUTH, IWL_STA_NONE);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot set state from AUTH to NONE: %s\n", zx_status_get_string(ret));
-    goto out;
+    return ret;
   }
 
-  // Tell firmware to flush all packets in the Tx queue. This must be done before we remove the STA
-  // (in the NONE->NOTEXIST transition).
-  // TODO(79799): understand why we need this.
-  mtx_lock(&mvmvif->mvm->mutex);
-  // Remove Time event (in case assoc failed)
-  ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
-  }
-  iwl_mvm_flush_sta(mvmvif->mvm, mvm_sta, false, 0);
-
-  // Update the MAC context in the firmware.
-  mvmvif->bss_conf.assoc = false;
-  ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
-  mtx_unlock(&mvmvif->mvm->mutex);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot update MAC context in the firmware: %s\n", zx_status_get_string(ret));
-    goto out;
-  }
-
-  ret = mac_unconfigure_bss(mvmvif, mvm_sta);
-
-out:
-  return ret;
+  return remove_chanctx(mvmvif);
 }
 
-// This function is to revert what mac_configure_bss() does.
-zx_status_t mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta) {
-  // mvmvif->phy_ctxt will be cleared up in iwl_mvm_unassign_vif_chanctx(). So backup the phy
-  // context ID for removing.
-  auto phy_ctxt_id = mvmvif->phy_ctxt->id;
-
-  // The 'ap_sta_id' will be reset in iwl_mvm_rm_sta() (via NONE --> NOTEXIST), back it up.
-  auto ap_sta_id = mvmvif->ap_sta_id;
-
-  // REMOVE_STA will be issued to remove the station entry in the firmware.
-  zx_status_t ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NONE, IWL_STA_NOTEXIST);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot set state from NONE to NOTEXIST: %s\n", zx_status_get_string(ret));
-    goto out;
-  }
-
-  // To simulate the behavior that iwl_mvm_bss_info_changed_station() would do for disassocitaion.
-  mtx_lock(&mvmvif->mvm->mutex);
-  memset(mvmvif->bss_conf.bssid, 0, ETH_ALEN);
-  memset(mvmvif->bssid, 0, ETH_ALEN);
-  // This will take the cleared BSSID from bss_conf and update the firmware.
-  ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
-  mtx_unlock(&mvmvif->mvm->mutex);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvm, "failed to update MAC (clear after unassoc)\n");
-    goto out;
-  }
-
-  // Unbinding MAC and PHY contexts.
-  ret = iwl_mvm_unassign_vif_chanctx(mvmvif);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "cannot unassign VIF channel context: %s\n", zx_status_get_string(ret));
-    goto out;
-  }
-
-  ret = iwl_mvm_remove_chanctx(mvmvif->mvm, phy_ctxt_id);
-  if (ret != ZX_OK) {
-    IWL_ERR(mvmvif, "Cannot remove channel context: %s\n", zx_status_get_string(ret));
-    goto out;
-  }
-
-out:
-  free_ap_mvm_sta(mvm_sta);
-  mvmvif->mvm->fw_id_to_mac_id[ap_sta_id] = NULL;
-
-  return ret;
-}
-
-zx_status_t mac_start_hw_scan(void* ctx, const wlan_hw_scan_config_t* scan_config) {
+zx_status_t mac_start_passive_scan(void* ctx,
+                                   const wlan_softmac_passive_scan_args_t* passive_scan_args,
+                                   uint64_t* out_scan_id) {
   const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
+  return iwl_mvm_mac_hw_scan_passive(mvmvif, passive_scan_args, out_scan_id);
+}
 
-  if (scan_config->scan_type != WLAN_HW_SCAN_TYPE_PASSIVE) {
-    IWL_ERR(ctx, "Unsupported scan type: %s\n", wlan_hw_scan_type_to_str(scan_config->scan_type));
-    return ZX_ERR_NOT_SUPPORTED;
-  }
-
-  return iwl_mvm_mac_hw_scan(mvmvif, scan_config);
+zx_status_t mac_start_active_scan(void* ctx,
+                                  const wlan_softmac_active_scan_args_t* active_scan_args,
+                                  uint64_t* out_scan_id) {
+  return ZX_ERR_NOT_SUPPORTED;
 }
 
 zx_status_t mac_init(void* ctx, struct iwl_trans* drvdata, zx_device_t* zxdev, uint16_t idx) {
@@ -750,22 +519,6 @@
   return status;
 }
 
-// TODO (fxbug.dev/63618) - to be removed.
-wlanmac_protocol_ops_t wlanmac_ops = {
-    .query = mac_query,
-    .start = mac_start,
-    .stop = mac_stop,
-    .queue_tx = mac_queue_tx,
-    .set_channel = mac_set_channel,
-    .configure_bss = mac_configure_bss,
-    .enable_beaconing = mac_enable_beaconing,
-    .configure_beacon = mac_configure_beacon,
-    .set_key = mac_set_key,
-    .configure_assoc = mac_configure_assoc,
-    .clear_assoc = mac_clear_assoc,
-    .start_hw_scan = mac_start_hw_scan,
-};
-
 void mac_unbind(void* ctx) {
   const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
 
@@ -789,30 +542,25 @@
   free(mvmvif);
 }
 
-// //TODO (fxbug.dev/63618) - to be removed.
-zx_protocol_device_t device_mac_ops = {
-    .version = DEVICE_OPS_VERSION,
-    .unbind = mac_unbind,
-    .release = mac_release,
-};
-
 /////////////////////////////////////       PHY       //////////////////////////////////////////////
 
-zx_status_t phy_query(void* ctx, wlanphy_impl_info_t* info) {
+zx_status_t phy_get_supported_mac_roles(
+    void* ctx,
+    wlan_mac_role_t out_supported_mac_roles_list[fuchsia_wlan_common_MAX_SUPPORTED_MAC_ROLES],
+    uint8_t* out_supported_mac_roles_count) {
   const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx);
   struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
-  if (!mvm || !info) {
+  if (nullptr == mvm || nullptr == out_supported_mac_roles_list ||
+      nullptr == out_supported_mac_roles_count) {
     return ZX_ERR_INVALID_ARGS;
   }
 
   struct iwl_nvm_data* nvm_data = mvm->nvm_data;
   ZX_ASSERT(nvm_data);
 
-  memset(info, 0, sizeof(*info));
-
   // TODO(fxbug.dev/36677): supports AP role
-  info->supported_mac_roles = WLAN_INFO_MAC_ROLE_CLIENT;
-
+  out_supported_mac_roles_list[0] = WLAN_MAC_ROLE_CLIENT;
+  *out_supported_mac_roles_count = 1;
   return ZX_OK;
 }
 
@@ -844,14 +592,14 @@
     return ZX_ERR_INVALID_ARGS;
   }
 
-  mtx_lock(&mvm->mutex);
+  auto lock = std::lock_guard(mvm->mutex);
 
   // Find the first empty mvmvif slot.
   int idx;
   ret = iwl_mvm_find_free_mvmvif_slot(mvm, &idx);
   if (ret != ZX_OK) {
     IWL_ERR(mvm, "cannot find an empty slot for new MAC interface\n");
-    goto unlock;
+    return ret;
   }
 
   // Allocate a MAC context. This will be initialized once iwl_mvm_mac_add_interface() is called.
@@ -860,7 +608,7 @@
   mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
   if (!mvmvif) {
     ret = ZX_ERR_NO_MEMORY;
-    goto unlock;
+    return ret;
   }
 
   // Set default values into the mvmvif
@@ -874,13 +622,11 @@
   if (ret != ZX_OK) {
     IWL_ERR(ctx, "Cannot assign the new mvmvif to MVM: %s\n", zx_status_get_string(ret));
     // The allocated mvmvif instance will be freed at mac_release().
-    goto unlock;
+    return ret;
   }
-  *out_iface_id = idx;
 
-unlock:
-  mtx_unlock(&mvm->mutex);
-  return ret;
+  *out_iface_id = idx;
+  return ZX_OK;
 }
 
 // If there are failures post phy_create_iface() and before phy_start_iface()
@@ -888,11 +634,13 @@
 void phy_create_iface_undo(struct iwl_trans* iwl_trans, uint16_t idx) {
   struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
 
-  // Unbind and free the mvmvif interface.
-  mtx_lock(&mvm->mutex);
-  struct iwl_mvm_vif* mvmvif = mvm->mvmvif[idx];
-  iwl_mvm_unbind_mvmvif(mvm, idx);
-  mtx_unlock(&mvm->mutex);
+  struct iwl_mvm_vif* mvmvif = nullptr;
+  {
+    // Unbind and free the mvmvif interface.
+    auto lock = std::lock_guard(mvm->mutex);
+    mvmvif = mvm->mvmvif[idx];
+    iwl_mvm_unbind_mvmvif(mvm, idx);
+  }
 
   free(mvmvif);
 }
@@ -902,7 +650,12 @@
   struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
   zx_status_t ret = ZX_OK;
 
-  mtx_lock(&mvm->mutex);
+  if (idx >= MAX_NUM_MVMVIF) {
+    IWL_ERR(mvm, "Interface index is too large (%d). expect less than %d\n", idx, MAX_NUM_MVMVIF);
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  auto lock = std::lock_guard(mvm->mutex);
   struct iwl_mvm_vif* mvmvif = mvm->mvmvif[idx];
   mvmvif->zxdev = zxdev;
 
@@ -918,7 +671,7 @@
       // TODO: It does not look clean to have unbind happen here.
       iwl_mvm_unbind_mvmvif(mvm, idx);
 
-      goto unlock;
+      return ret;
     }
 
     // Once MVM is started, copy the MAC address to mvmvif.
@@ -926,9 +679,7 @@
     memcpy(mvmvif->addr, nvm_data->hw_addr, ETH_ALEN);
   }
 
-unlock:
-  mtx_unlock(&mvm->mutex);
-  return ret;
+  return ZX_OK;
 }
 
 // This function is working with a PHY context ('ctx') to delete a MAC interface ('id').
@@ -937,43 +688,39 @@
   const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx);
   struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
   struct iwl_mvm_vif* mvmvif = nullptr;
-  zx_status_t ret = ZX_OK;
 
   if (!mvm) {
     IWL_ERR(mvm, "cannot obtain MVM from ctx=%p while destroying interface (%d)\n", ctx, id);
     return ZX_ERR_INVALID_ARGS;
   }
 
-  mtx_lock(&mvm->mutex);
+  {
+    auto lock = std::lock_guard(mvm->mutex);
 
-  if (id >= MAX_NUM_MVMVIF) {
-    IWL_ERR(mvm, "the interface id (%d) is invalid\n", id);
-    ret = ZX_ERR_INVALID_ARGS;
-    goto unlock;
-  }
+    if (id >= MAX_NUM_MVMVIF) {
+      IWL_ERR(mvm, "the interface id (%d) is invalid\n", id);
+      return ZX_ERR_INVALID_ARGS;
+    }
 
-  mvmvif = mvm->mvmvif[id];
-  if (!mvmvif) {
-    IWL_ERR(mvm, "the interface id (%d) has no MAC context\n", id);
-    ret = ZX_ERR_NOT_FOUND;
-    goto unlock;
-  }
+    mvmvif = mvm->mvmvif[id];
+    if (!mvmvif) {
+      IWL_ERR(mvm, "the interface id (%d) has no MAC context\n", id);
+      return ZX_ERR_NOT_FOUND;
+    }
 
-  // Unlink the 'mvmvif' from the 'mvm'. The zxdev will be removed in mac_unbind(),
-  // and the memory of 'mvmvif' will be freed in mac_release().
-  iwl_mvm_unbind_mvmvif(mvm, id);
+    // Unlink the 'mvmvif' from the 'mvm'. The zxdev will be removed in mac_unbind(),
+    // and the memory of 'mvmvif' will be freed in mac_release().
+    iwl_mvm_unbind_mvmvif(mvm, id);
 
-  // the last MAC interface. stop the MVM to save power. 'vif_count' had been decreased in
-  // iwl_mvm_mac_remove_interface().
-  if (mvm->vif_count == 0) {
-    __iwl_mvm_mac_stop(mvm);
+    // the last MAC interface. stop the MVM to save power. 'vif_count' had been decreased in
+    // iwl_mvm_mac_remove_interface().
+    if (mvm->vif_count == 0) {
+      __iwl_mvm_mac_stop(mvm);
+    }
   }
 
   device_async_remove(mvmvif->zxdev);
-
-unlock:
-  mtx_unlock(&mvm->mutex);
-  return ret;
+  return ZX_OK;
 }
 
 zx_status_t phy_set_country(void* ctx, const wlanphy_country_t* country) {
diff --git a/third_party/iwlwifi/platform/mvm-mlme.h b/third_party/iwlwifi/platform/mvm-mlme.h
index 98649cd..90bfc57 100644
--- a/third_party/iwlwifi/platform/mvm-mlme.h
+++ b/third_party/iwlwifi/platform/mvm-mlme.h
@@ -7,15 +7,14 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_MVM_MLME_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_MVM_MLME_H_
 
-#include <fuchsia/hardware/wlan/mac/cpp/banjo.h>
+#include <fuchsia/hardware/wlan/phyinfo/cpp/banjo.h>
+#include <fuchsia/hardware/wlan/softmac/cpp/banjo.h>
 #include <fuchsia/hardware/wlanphyimpl/cpp/banjo.h>
 #include <fuchsia/wlan/common/cpp/banjo.h>
 #include <fuchsia/wlan/ieee80211/c/banjo.h>
 #include <fuchsia/wlan/internal/cpp/banjo.h>
 #include <lib/ddk/device.h>
 
-#include <fuchsia/hardware/wlanphyinfo/cpp/banjo.h>
-
 #if defined(__cplusplus)
 extern "C" {
 #endif  // defined(__cplusplus)
@@ -23,8 +22,8 @@
 // IEEE Std 802.11-2016, Table 9-19
 #define WLAN_MSDU_MAX_LEN 2304UL
 
-extern wlanmac_protocol_ops_t wlanmac_ops;
-extern zx_protocol_device_t device_mac_ops;  // for testing only
+struct iwl_mvm_vif;
+struct iwl_mvm_sta;
 
 // for testing
 size_t compose_band_list(const struct iwl_nvm_data* nvm_data,
@@ -33,7 +32,10 @@
                      size_t bands_count, wlan_info_band_info_t* band_infos);
 
 // Phy protocol helpers
-zx_status_t phy_query(void* ctx, wlanphy_impl_info_t* info);
+zx_status_t phy_get_supported_mac_roles(
+    void* ctx,
+    wlan_mac_role_t out_supported_mac_roles_list[fuchsia_wlan_common_MAX_SUPPORTED_MAC_ROLES],
+    uint8_t* out_supported_mac_roles_count);
 zx_status_t phy_create_iface(void* ctx, const wlanphy_impl_create_iface_req_t* req,
                              uint16_t* out_iface_id);
 zx_status_t phy_start_iface(void* ctx, zx_device_t* zxdev, uint16_t idx);
@@ -44,19 +46,28 @@
 void phy_create_iface_undo(struct iwl_trans* iwl_trans, uint16_t idx);
 
 // Mac protocol helpers
-zx_status_t mac_query(void* ctx, uint32_t options, wlanmac_info_t* info);
-zx_status_t mac_start(void* ctx, const wlanmac_ifc_protocol_t* ifc, zx_handle_t* out_mlme_channel);
-void mac_stop(void* ctx);
-zx_status_t mac_queue_tx(void* ctx, uint32_t options, const wlan_tx_packet_t* pkt);
-zx_status_t mac_set_channel(void* ctx, uint32_t options, const wlan_channel_t* channel);
-zx_status_t mac_configure_bss(void* ctx, uint32_t options, const bss_config_t* config);
+zx_status_t mac_query(void* ctx, uint32_t options, wlan_softmac_info_t* info);
+zx_status_t mac_start(void* ctx, const wlan_softmac_ifc_protocol_t* ifc,
+                      zx_handle_t* out_mlme_channel);
+void mac_stop(struct iwl_mvm_vif* mvmvif);
+zx_status_t mac_set_channel(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                            const wlan_channel_t* channel);
+zx_status_t mac_configure_bss(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                              const bss_config_t* config);
+zx_status_t mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif);
 zx_status_t mac_enable_beaconing(void* ctx, uint32_t options, const wlan_bcn_config_t* bcn_cfg);
-zx_status_t mac_configure_beacon(void* ctx, uint32_t options, const wlan_tx_packet_t* pkt);
-zx_status_t mac_set_key(void* ctx, uint32_t options, const wlan_key_config_t* key_config);
-zx_status_t mac_configure_assoc(void* ctx, uint32_t options, const wlan_assoc_ctx_t* assoc_ctx);
-zx_status_t mac_clear_assoc(void* ctx, uint32_t options,
+zx_status_t mac_configure_beacon(void* ctx, uint32_t options,
+                                 const wlan_tx_packet_t* packet_template);
+zx_status_t mac_configure_assoc(struct iwl_mvm_vif* mvmvif, uint32_t options,
+                                const wlan_assoc_ctx_t* assoc_ctx);
+zx_status_t mac_clear_assoc(struct iwl_mvm_vif* mvmvif, uint32_t options,
                             const uint8_t peer_addr[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]);
-zx_status_t mac_start_hw_scan(void* ctx, const wlan_hw_scan_config_t* scan_config);
+zx_status_t mac_start_passive_scan(void* ctx,
+                                   const wlan_softmac_passive_scan_args_t* passive_scan_args,
+                                   uint64_t* out_scan_id);
+zx_status_t mac_start_active_scan(void* ctx,
+                                  const wlan_softmac_active_scan_args_t* active_scan_args,
+                                  uint64_t* out_scan_id);
 zx_status_t mac_init(void* ctx, struct iwl_trans* drvdata, zx_device_t* zxdev, uint16_t idx);
 void mac_unbind(void* ctx);
 void mac_release(void* ctx);
diff --git a/third_party/iwlwifi/platform/mvm-sta.cc b/third_party/iwlwifi/platform/mvm-sta.cc
new file mode 100644
index 0000000..2cdf130
--- /dev/null
+++ b/third_party/iwlwifi/platform/mvm-sta.cc
@@ -0,0 +1,276 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/platform/mvm-sta.h"
+
+#include <fidl/fuchsia.wlan.ieee80211/cpp/wire.h>
+#include <threads.h>
+#include <zircon/errors.h>
+#include <zircon/status.h>
+
+#include <algorithm>
+#include <cstring>
+
+extern "C" {
+#include "third_party/iwlwifi/mvm/sta.h"
+}  // extern "C"
+
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/mvm-mlme.h"
+#include "third_party/iwlwifi/platform/rcu.h"
+
+namespace wlan::iwlwifi {
+namespace {
+
+// IEEE 802.11-2016 3.2 (c.f. "vendor organizationally unique identifier")
+constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
+
+}  // namespace
+
+MvmSta::MvmSta(struct iwl_mvm_vif* iwl_mvm_vif, std::unique_ptr<struct iwl_mvm_sta> iwl_mvm_sta)
+    : iwl_mvm_vif_(iwl_mvm_vif), iwl_mvm_sta_(std::move(iwl_mvm_sta)) {}
+
+MvmSta::~MvmSta() {
+  if (iwl_mvm_sta_ != nullptr) {
+    zx_status_t status = ZX_OK;
+
+    for (auto& key_conf : ieee80211_key_confs_) {
+      if (key_conf == nullptr) {
+        continue;
+      }
+      if ((status = iwl_mvm_mac_remove_key(iwl_mvm_sta_->mvmvif, iwl_mvm_sta_.get(),
+                                           key_conf.get())) != ZX_OK) {
+        IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_remove_key() failed for keyidx %d: %s\n",
+                key_conf->keyidx, zx_status_get_string(status));
+      }
+      key_conf.reset();
+    }
+
+    if ((status = ChangeState(iwl_sta_state::IWL_STA_NOTEXIST)) != ZX_OK) {
+      IWL_ERR(iwl_mvm_vif_, "ChangeState() failed: %s\n", zx_status_get_string(status));
+    }
+
+    iwl_rcu_call_sync(
+        iwl_mvm_vif_->mvm->dev,
+        [](void* data) {
+          auto mvm_sta = reinterpret_cast<struct iwl_mvm_sta*>(data);
+          for (auto txq : mvm_sta->txq) {
+            delete txq;
+          }
+          delete mvm_sta;
+        },
+        iwl_mvm_sta_.release());
+  }
+}
+
+// static
+zx_status_t MvmSta::Create(struct iwl_mvm_vif* iwl_mvm_vif, const uint8_t bssid[ETH_ALEN],
+                           std::unique_ptr<MvmSta>* mvm_sta_out) {
+  zx_status_t status = ZX_OK;
+
+  // Initialize the iwl_mvm_sta instance.
+  auto iwl_mvm_sta = std::make_unique<struct iwl_mvm_sta>();
+  for (auto& txq_ref : iwl_mvm_sta->txq) {
+    txq_ref = new struct iwl_mvm_txq();
+  }
+  static_assert(sizeof(iwl_mvm_sta->addr) == sizeof(*bssid) * ETH_ALEN);
+  std::memcpy(iwl_mvm_sta->addr, bssid, sizeof(iwl_mvm_sta->addr));
+  mtx_init(&iwl_mvm_sta->lock, mtx_plain);
+
+  auto mvm_sta = std::unique_ptr<MvmSta>(new MvmSta(iwl_mvm_vif, std::move(iwl_mvm_sta)));
+
+  if ((status = mvm_sta->ChangeState(iwl_sta_state::IWL_STA_NONE)) != ZX_OK) {
+    return status;
+  }
+
+  {
+    // Allocate a TX queue for this station.
+    auto iwl_mvm = mvm_sta->iwl_mvm_vif_->mvm;
+    auto lock = std::lock_guard(iwl_mvm->mutex);
+    if ((status = iwl_mvm_sta_alloc_queue(iwl_mvm, mvm_sta->iwl_mvm_sta_.get(), IEEE80211_AC_BE,
+                                          IWL_MAX_TID_COUNT)) != ZX_OK) {
+      mtx_unlock(&iwl_mvm->mutex);
+      IWL_ERR(iwl_mvm, "iwl_mvm_sta_alloc_queue() failed: %s\n", zx_status_get_string(status));
+      return status;
+    }
+  }
+
+  *mvm_sta_out = std::move(mvm_sta);
+  return status;
+}
+
+zx_status_t MvmSta::SetKey(const struct wlan_key_config* key_config) {
+  zx_status_t status = ZX_OK;
+  struct iwl_mvm* const mvm = iwl_mvm_sta_->mvmvif->mvm;
+
+  if (mvm->trans->cfg->gen2 || iwl_mvm_has_new_tx_api(mvm)) {
+    // The new firmwares (for starting with the 22000 series) have different packet generation
+    // requirements than mentioned below.
+    return ZX_ERR_NOT_SUPPORTED;
+  }
+
+  if (!std::equal(key_config->cipher_oui,
+                  key_config->cipher_oui + std::size(key_config->cipher_oui), kIeeeOui,
+                  kIeeeOui + std::size(kIeeeOui))) {
+    // IEEE 802.11-2016 9.4.2.25.2
+    // The standard ciphers all live in the IEEE space.
+    return ZX_ERR_NOT_SUPPORTED;
+  }
+
+  if (key_config->key_type < 0 || key_config->key_type >= ieee80211_key_confs_.size()) {
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  // Remove any existing key in this slot.
+  if (ieee80211_key_confs_[key_config->key_type] != nullptr) {
+    if ((status = iwl_mvm_mac_remove_key(iwl_mvm_sta_->mvmvif, iwl_mvm_sta_.get(),
+                                         ieee80211_key_confs_[key_config->key_type].get())) !=
+        ZX_OK) {
+      IWL_ERR(mvmvif, "iwl_mvm_mac_remove_key() failed: %s\n", zx_status_get_string(status));
+      return status;
+    }
+    ieee80211_key_confs_[key_config->key_type].reset();
+  }
+
+  unique_free_ptr<struct ieee80211_key_conf> key_conf(reinterpret_cast<struct ieee80211_key_conf*>(
+      malloc(sizeof(ieee80211_key_conf) + key_config->key_len)));
+  memset(key_conf.get(), 0, sizeof(*key_conf) + key_config->key_len);
+  key_conf->cipher = key_config->cipher_type;
+  key_conf->keyidx = key_config->key_idx;
+  key_conf->keylen = key_config->key_len;
+  key_conf->rx_seq = key_config->rsc;
+  memcpy(key_conf->key, key_config->key, key_conf->keylen);
+
+  if ((status = iwl_mvm_mac_add_key(iwl_mvm_sta_->mvmvif, iwl_mvm_sta_.get(), key_conf.get())) !=
+      ZX_OK) {
+    IWL_ERR(mvmvif, "iwl_mvm_mac_add_key(key_type %d, cipher_type %d, key_idx %d) failed: %s\n",
+            key_config->key_type, key_config->cipher_type, key_config->key_idx,
+            zx_status_get_string(status));
+    return status;
+  }
+
+  ieee80211_key_confs_[key_config->key_type] = std::move(key_conf);
+  return ZX_OK;
+}
+
+struct ieee80211_key_conf* MvmSta::GetKey(wlan_key_type_t key_type) {
+  if (key_type < 0 || key_type > ieee80211_key_confs_.size()) {
+    return nullptr;
+  }
+  return ieee80211_key_confs_[key_type].get();
+}
+
+const struct ieee80211_key_conf* MvmSta::GetKey(wlan_key_type_t key_type) const {
+  if (key_type < 0 || key_type > ieee80211_key_confs_.size()) {
+    return nullptr;
+  }
+  return ieee80211_key_confs_[key_type].get();
+}
+
+enum iwl_sta_state MvmSta::GetState() const { return sta_state_; }
+
+zx_status_t MvmSta::ChangeState(enum iwl_sta_state state) {
+  zx_status_t status = ZX_OK;
+  while (state > sta_state_) {
+    if ((status = ChangeStateUp()) != ZX_OK) {
+      return status;
+    }
+  }
+  while (state < sta_state_) {
+    if ((status = ChangeStateDown()) != ZX_OK) {
+      return status;
+    }
+  }
+  return ZX_OK;
+}
+
+struct iwl_mvm_sta* MvmSta::iwl_mvm_sta() {
+  return iwl_mvm_sta_.get();
+}
+
+const struct iwl_mvm_sta* MvmSta::iwl_mvm_sta() const { return iwl_mvm_sta_.get(); }
+
+zx_status_t MvmSta::ChangeStateUp() {
+  zx_status_t status = ZX_OK;
+  iwl_sta_state new_state = iwl_sta_state::IWL_STA_NOTEXIST;
+  switch (sta_state_) {
+    case iwl_sta_state::IWL_STA_NOTEXIST: {
+      new_state = iwl_sta_state::IWL_STA_NONE;
+      break;
+    }
+    case iwl_sta_state::IWL_STA_NONE: {
+      new_state = iwl_sta_state::IWL_STA_AUTH;
+      break;
+    }
+    case iwl_sta_state::IWL_STA_AUTH: {
+      new_state = iwl_sta_state::IWL_STA_ASSOC;
+      break;
+    }
+    case iwl_sta_state::IWL_STA_ASSOC: {
+      new_state = iwl_sta_state::IWL_STA_AUTHORIZED;
+      break;
+    }
+    default: {
+      IWL_ERR(iwl_mvm_vif_, "ChangeStateUp() in invalid state %d\n", sta_state_);
+      return ZX_ERR_BAD_STATE;
+    }
+  }
+
+  if ((status = iwl_mvm_mac_sta_state(iwl_mvm_vif_, iwl_mvm_sta_.get(), sta_state_, new_state)) !=
+      ZX_OK) {
+    IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_sta_state() failed for %d -> %d: %s\n", sta_state_,
+            new_state, zx_status_get_string(status));
+    return status;
+  }
+
+  sta_state_ = new_state;
+  return ZX_OK;
+}
+
+zx_status_t MvmSta::ChangeStateDown() {
+  zx_status_t status = ZX_OK;
+  iwl_sta_state new_state = iwl_sta_state::IWL_STA_NOTEXIST;
+  switch (sta_state_) {
+    case iwl_sta_state::IWL_STA_AUTHORIZED: {
+      new_state = iwl_sta_state::IWL_STA_ASSOC;
+      break;
+    }
+    case iwl_sta_state::IWL_STA_ASSOC: {
+      new_state = iwl_sta_state::IWL_STA_AUTH;
+      break;
+    }
+    case iwl_sta_state::IWL_STA_AUTH: {
+      new_state = iwl_sta_state::IWL_STA_NONE;
+      break;
+    }
+    case iwl_sta_state::IWL_STA_NONE: {
+      {
+        // Tell firmware to flush all packets in the Tx queue. This must be done before we remove
+        // the STA (in the NONE->NOTEXIST transition).
+        // TODO(79799): understand why we need this.
+        auto lock = std::lock_guard(iwl_mvm_vif_->mvm->mutex);
+        iwl_mvm_flush_sta(iwl_mvm_vif_->mvm, iwl_mvm_sta_.get(), false, 0);
+      }
+
+      new_state = iwl_sta_state::IWL_STA_NOTEXIST;
+      break;
+    }
+    default: {
+      IWL_ERR(iwl_mvm_vif_, "ChangeStateDown() in invalid state %d\n", sta_state_);
+      return ZX_ERR_BAD_STATE;
+    }
+  }
+
+  if ((status = iwl_mvm_mac_sta_state(iwl_mvm_vif_, iwl_mvm_sta_.get(), sta_state_, new_state)) !=
+      ZX_OK) {
+    IWL_ERR(iwl_mvm_vif_, "iwl_mvm_mac_sta_state() failed for %d -> %d: %s\n", sta_state_,
+            new_state, zx_status_get_string(status));
+    return status;
+  }
+
+  sta_state_ = new_state;
+  return ZX_OK;
+}
+
+}  // namespace wlan::iwlwifi
diff --git a/third_party/iwlwifi/platform/mvm-sta.h b/third_party/iwlwifi/platform/mvm-sta.h
new file mode 100644
index 0000000..6d9445a
--- /dev/null
+++ b/third_party/iwlwifi/platform/mvm-sta.h
@@ -0,0 +1,72 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_MVM_STA_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_MVM_STA_H_
+
+#include <fuchsia/hardware/wlan/associnfo/cpp/banjo.h>
+#include <fuchsia/hardware/wlan/softmac/cpp/banjo.h>
+#include <netinet/if_ether.h>
+#include <zircon/types.h>
+
+#include <array>
+#include <cstdlib>
+#include <memory>
+#include <type_traits>
+
+extern "C" {
+#include "third_party/iwlwifi/mvm/mvm.h"
+}  // extern "C"
+
+#include "third_party/iwlwifi/platform/scoped_utils.h"
+
+struct ieee80211_key_conf;
+struct iwl_mvm_sta;
+
+namespace wlan::iwlwifi {
+
+// This class manages the lifetime of one instance of a iwl_mvm_sta, including associated state such
+// as encryption keys.
+class MvmSta {
+ public:
+  // Factory function for MvmSta instances.
+  static zx_status_t Create(struct iwl_mvm_vif* iwl_mvm_vif, const uint8_t bssid[ETH_ALEN],
+                            std::unique_ptr<MvmSta>* mvm_sta_out);
+  ~MvmSta();
+
+  // Set one of the keys for this station, which may be the pairwise, group, etc. key.
+  zx_status_t SetKey(const struct wlan_key_config* key_config);
+
+  // Get a key for this station, which may be used for TX.
+  struct ieee80211_key_conf* GetKey(wlan_key_type_t key_type);
+  const struct ieee80211_key_conf* GetKey(wlan_key_type_t key_type) const;
+
+  // Get the current station state.
+  enum iwl_sta_state GetState() const;
+
+  // Effect a change to the given station state.
+  zx_status_t ChangeState(enum iwl_sta_state state);
+
+  // Accessors.
+  struct iwl_mvm_sta* iwl_mvm_sta();
+  const struct iwl_mvm_sta* iwl_mvm_sta() const;
+
+ private:
+  explicit MvmSta(struct iwl_mvm_vif* iwl_mvm_vif, std::unique_ptr<struct iwl_mvm_sta> iwl_mvm_sta);
+
+  // Change the station state in each direction:
+  // * Up is from NOTEXIST -> AUTHORIZED
+  // * Down is from AUTHORIZED -> NOTEXIST
+  zx_status_t ChangeStateUp();
+  zx_status_t ChangeStateDown();
+
+  struct iwl_mvm_vif* iwl_mvm_vif_ = nullptr;
+  std::unique_ptr<struct iwl_mvm_sta> iwl_mvm_sta_;
+  std::array<unique_free_ptr<struct ieee80211_key_conf>, 5> ieee80211_key_confs_;
+  enum iwl_sta_state sta_state_ = iwl_sta_state::IWL_STA_NOTEXIST;
+};
+
+}  // namespace wlan::iwlwifi
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_MVM_STA_H_
diff --git a/third_party/iwlwifi/platform/pci.h b/third_party/iwlwifi/platform/pci.h
index 78c0038..5485bd3 100644
--- a/third_party/iwlwifi/platform/pci.h
+++ b/third_party/iwlwifi/platform/pci.h
@@ -8,7 +8,7 @@
 // This file contains PCI bus code that operates as a compatibility layer between the Linux and
 // Fuchsia PCI bus driver models.
 
-#include "kernel.h"
+#include "third_party/iwlwifi/platform/kernel.h"
 
 #if defined(__cplusplus)
 extern "C" {
diff --git a/third_party/iwlwifi/platform/pcie-device.cc b/third_party/iwlwifi/platform/pcie-device.cc
index 80605cf..be5e98d 100644
--- a/third_party/iwlwifi/platform/pcie-device.cc
+++ b/third_party/iwlwifi/platform/pcie-device.cc
@@ -2,13 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
 #include "third_party/iwlwifi/platform/pcie-device.h"
 
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/async-loop/default.h>
+#include <lib/async/cpp/task.h>
 #include <zircon/assert.h>
 #include <zircon/status.h>
 
@@ -21,6 +19,7 @@
 }
 
 #include "third_party/iwlwifi/platform/driver-inspector.h"
+#include "third_party/iwlwifi/platform/rcu-manager.h"
 
 #if !CPTCFG_IWLMVM
 #error "PcieDevice requires support for MVM firmwares."
@@ -72,11 +71,16 @@
               zx_status_get_string(status));
       return status;
     }
+    rcu_manager_ = std::make_unique<RcuManager>(task_loop_->dispatcher());
+    rcu_manager_->InitForThread();
+    ::async::PostTask(task_loop_->dispatcher(), [this]() { rcu_manager_->InitForThread(); });
+    ::async::PostTask(irq_loop_->dispatcher(), [this]() { rcu_manager_->InitForThread(); });
 
     // Fill in the relevant Fuchsia-specific fields in our driver interface struct.
     pci_dev_.dev.zxdev = zxdev();
     pci_dev_.dev.task_dispatcher = task_loop_->dispatcher();
     pci_dev_.dev.irq_dispatcher = irq_loop_->dispatcher();
+    pci_dev_.dev.rcu_manager = static_cast<struct rcu_manager*>(rcu_manager_.get());
     pci_dev_.dev.inspector = static_cast<struct driver_inspector*>(driver_inspector_.get());
 
     if ((status = device_get_fragment_protocol(parent(), "pci", ZX_PROTOCOL_PCI,
diff --git a/third_party/iwlwifi/platform/pcie-device.h b/third_party/iwlwifi/platform/pcie-device.h
index 2f08f40..80f16c6 100644
--- a/third_party/iwlwifi/platform/pcie-device.h
+++ b/third_party/iwlwifi/platform/pcie-device.h
@@ -19,6 +19,7 @@
 namespace wlan::iwlwifi {
 
 class DriverInspector;
+class RcuManager;
 
 // This class contains the Fuchsia-specific PCIE bus initialization logic, using the DDKTL classes
 // to manage the lifetime of a iwlwifi driver instance.
@@ -44,6 +45,7 @@
   std::unique_ptr<DriverInspector> driver_inspector_;
   std::unique_ptr<::async::Loop> task_loop_;
   std::unique_ptr<::async::Loop> irq_loop_;
+  std::unique_ptr<RcuManager> rcu_manager_;
   iwl_pci_dev pci_dev_;
 };
 
diff --git a/third_party/iwlwifi/platform/rcu-manager.cc b/third_party/iwlwifi/platform/rcu-manager.cc
new file mode 100644
index 0000000..8ca9bd5
--- /dev/null
+++ b/third_party/iwlwifi/platform/rcu-manager.cc
@@ -0,0 +1,76 @@
+#include "third_party/iwlwifi/platform/rcu-manager.h"
+
+#include <lib/async/cpp/task.h>
+#include <lib/stdcompat/atomic.h>
+#include <zircon/assert.h>
+
+#include <limits>
+
+namespace wlan::iwlwifi {
+
+// static
+thread_local int RcuManager::read_lock_count_ = 0;
+
+RcuManager::RcuManager(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
+
+RcuManager::~RcuManager() {
+  zx_status_t status = ZX_OK;
+
+  // Wait for all existing calls to complete.
+  cpp20::atomic_ref<zx_futex_t> call_count_ref(call_count_);
+  zx_futex_t count = call_count_ref.load(std::memory_order_acquire);
+  while (count > 0) {
+    if ((status = zx_futex_wait(&call_count_, count, ZX_HANDLE_INVALID, ZX_TIME_INFINITE)) !=
+        ZX_OK) {
+      if (status != ZX_ERR_BAD_STATE) {
+        break;
+      }
+    }
+    count = call_count_ref.load(std::memory_order_acquire);
+  }
+}
+
+void RcuManager::InitForThread() { read_lock_count_ = 0; }
+
+void RcuManager::ReadLock() {
+  if (++read_lock_count_ == 1) {
+    rwlock_.lock_shared();
+  }
+}
+
+void RcuManager::ReadUnlock() {
+  ZX_DEBUG_ASSERT(read_lock_count_ > 0);
+  if (--read_lock_count_ == 0) {
+    rwlock_.unlock_shared();
+  }
+}
+
+void RcuManager::Sync() {
+  // Sync only has to ensure that there are no more outstanding reader locks.
+  rwlock_.lock();
+  rwlock_.unlock();
+}
+
+void RcuManager::CallSync(void (*func)(void*), void* data) {
+  // Post the task to the worker dispatcher.  This has the advantages of:
+  // * Not immediately blocking the current thread.
+  // * The worker dispatcher is often another thread that uses RCUs.  By posting the task to this
+  //   thread, we ensure that it cannot also be locking for RCU at the same time, thus reducing
+  //   contention.
+  cpp20::atomic_ref<zx_futex_t> call_count_ref(call_count_);
+  call_count_ref.fetch_add(1, std::memory_order_release);
+  ::async::PostTask(dispatcher_, [this, func, data]() {
+    Sync();
+    func(data);
+
+    // Signal waiters that may be waiting for all calls to complete.
+    cpp20::atomic_ref<zx_futex_t> call_count_ref(call_count_);
+    if (call_count_ref.fetch_sub(1, std::memory_order_release) == 1) {
+      zx_futex_wake(&call_count_, std::numeric_limits<uint32_t>::max());
+    }
+  });
+}
+
+void RcuManager::FreeSync(void* alloc) { CallSync(&free, alloc); }
+
+}  // namespace wlan::iwlwifi
diff --git a/third_party/iwlwifi/platform/rcu-manager.h b/third_party/iwlwifi/platform/rcu-manager.h
new file mode 100644
index 0000000..1a71268
--- /dev/null
+++ b/third_party/iwlwifi/platform/rcu-manager.h
@@ -0,0 +1,53 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_RCU_MANAGER_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_RCU_MANAGER_H_
+
+#include <lib/async/dispatcher.h>
+#include <zircon/types.h>
+
+#include <shared_mutex>
+
+namespace wlan::iwlwifi {
+
+// This class manages RCU-based synchronization for a set of threads.
+class RcuManager {
+ public:
+  explicit RcuManager(async_dispatcher_t* dispatcher);
+  ~RcuManager();
+
+  // Initialize the RCU manager for the current thread.  This must be run on every thread that will
+  // be using RCU.
+  void InitForThread();
+
+  // Enter a RCU read-side lock.
+  void ReadLock();
+
+  // Exit a RCU read-side lock.
+  void ReadUnlock();
+
+  // Wait for all existing read-side locks to unlock.
+  void Sync();
+
+  // Helper: call a function after synchronizing.
+  void CallSync(void (*func)(void*), void* data);
+
+  // Helper: free() an allocation after synchronizing.
+  void FreeSync(void* alloc);
+
+ private:
+  async_dispatcher_t* dispatcher_ = nullptr;
+  std::shared_mutex rwlock_;
+  static thread_local int read_lock_count_;
+  zx_futex_t call_count_ = 0;
+};
+
+}  // namespace wlan::iwlwifi
+
+// This subclass-as-an-alias exists purely to be compatible with C code that uses the
+// `rcu_manager` type as a struct pointer.
+struct rcu_manager final : public wlan::iwlwifi::RcuManager {};
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_RCU_MANAGER_H_
diff --git a/third_party/iwlwifi/platform/rcu.cc b/third_party/iwlwifi/platform/rcu.cc
new file mode 100644
index 0000000..4affa0f
--- /dev/null
+++ b/third_party/iwlwifi/platform/rcu.cc
@@ -0,0 +1,20 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/platform/rcu.h"
+
+#include "third_party/iwlwifi/platform/kernel.h"
+#include "third_party/iwlwifi/platform/rcu-manager.h"
+
+void iwl_rcu_read_lock(struct device* dev) { dev->rcu_manager->ReadLock(); }
+
+void iwl_rcu_read_unlock(struct device* dev) { dev->rcu_manager->ReadUnlock(); }
+
+void iwl_rcu_sync(struct device* dev) { dev->rcu_manager->Sync(); }
+
+void iwl_rcu_call_sync(struct device* dev, iwl_rcu_call_func func, void* data) {
+  dev->rcu_manager->CallSync(func, data);
+}
+
+void iwl_rcu_free_sync(struct device* dev, void* alloc) { dev->rcu_manager->FreeSync(alloc); }
diff --git a/third_party/iwlwifi/platform/rcu.h b/third_party/iwlwifi/platform/rcu.h
new file mode 100644
index 0000000..1c6ea77
--- /dev/null
+++ b/third_party/iwlwifi/platform/rcu.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_RCU_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_RCU_H_
+
+#include "third_party/iwlwifi/platform/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif  // defined(__cplusplus)
+
+struct device;
+
+typedef void (*iwl_rcu_call_func)(void*);
+
+// Enter a RCU read-side lock.  May be nested.
+void iwl_rcu_read_lock(struct device* dev);
+
+// Exit a RCU read-side lock.  May be nested.
+void iwl_rcu_read_unlock(struct device* dev);
+
+// Wait for all existing read-side locks to unlock.
+void iwl_rcu_sync(struct device* dev);
+
+// Call a function after synchronizing with existing read-side locks.
+void iwl_rcu_call_sync(struct device* dev, iwl_rcu_call_func func, void* data);
+
+// Free an allocation after synchronizing with existing read-side locks.
+void iwl_rcu_free_sync(struct device* dev, void* alloc);
+
+#define iwl_rcu_load(p) (atomic_load_explicit((_Atomic(__typeof(p))*)&p, memory_order_acquire))
+#define iwl_rcu_store(p, v) \
+  (atomic_store_explicit((_Atomic(__typeof(p))*)&p, v, memory_order_release))
+#define iwl_rcu_exchange(p, v) \
+  (atomic_exchange_explicit((_Atomic(__typeof(p))*)&p, v, memory_order_acq_rel))
+
+#if defined(__cplusplus)
+}  // extern "C"
+#endif  // defined(__cplusplus)
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_RCU_H_
diff --git a/third_party/iwlwifi/platform/scoped_utils.h b/third_party/iwlwifi/platform/scoped_utils.h
new file mode 100644
index 0000000..7603e9a
--- /dev/null
+++ b/third_party/iwlwifi/platform/scoped_utils.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_SCOPED_UTILS_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_SCOPED_UTILS_H_
+
+// This file contains a collection of utilities for owning a resource for the duration of a scope.
+
+#include <threads.h>
+#include <zircon/compiler.h>
+
+#include <cstdlib>
+#include <memory>
+#include <mutex>
+#include <type_traits>
+
+namespace std {
+
+// A specialization for std::lock_guard<> for use with mtx_t types.
+template <>
+class __TA_SCOPED_CAPABILITY lock_guard<mtx_t> {
+ public:
+  using mutex_type = mtx_t;
+  __WARN_UNUSED_CONSTRUCTOR explicit lock_guard(mtx_t& m) __TA_ACQUIRE(m) : m(m) { mtx_lock(&m); }
+  __WARN_UNUSED_CONSTRUCTOR lock_guard(mtx_t& m, std::adopt_lock_t t) __TA_REQUIRES(m) : m(m) {}
+  ~lock_guard() __TA_RELEASE() { mtx_unlock(&m); }
+  lock_guard(const lock_guard&) = delete;
+  lock_guard& operator=(const lock_guard&) = delete;
+
+ private:
+  mutex_type& m;
+};
+
+}  // namespace std
+
+namespace wlan::iwlwifi {
+
+// unique_free_ptr is like std::unique_ptr<>, but uses free() to destroy the held object.
+template <typename T>
+using unique_free_ptr = std::unique_ptr<T, std::integral_constant<decltype(std::free)*, std::free>>;
+
+}  // namespace wlan::iwlwifi
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_SCOPED_UTILS_H_
diff --git a/third_party/iwlwifi/platform/task-internal.cc b/third_party/iwlwifi/platform/task-internal.cc
index 64b2862..7272f0c 100644
--- a/third_party/iwlwifi/platform/task-internal.cc
+++ b/third_party/iwlwifi/platform/task-internal.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "task-internal.h"
+#include "third_party/iwlwifi/platform/task-internal.h"
 
 #include <lib/async/time.h>
 #include <lib/stdcompat/atomic.h>
diff --git a/third_party/iwlwifi/platform/task.cc b/third_party/iwlwifi/platform/task.cc
index bcbcf62..5c9aee0 100644
--- a/third_party/iwlwifi/platform/task.cc
+++ b/third_party/iwlwifi/platform/task.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "task.h"
+#include "third_party/iwlwifi/platform/task.h"
 
 #include <lib/async/dispatcher.h>
 
 #include <memory>
 
-#include "task-internal.h"
+#include "third_party/iwlwifi/platform/task-internal.h"
 
 struct iwl_task : public wlan::iwlwifi::TaskInternal {
  public:
diff --git a/third_party/iwlwifi/platform/task.h b/third_party/iwlwifi/platform/task.h
index 48c6c53..da0e9ae 100644
--- a/third_party/iwlwifi/platform/task.h
+++ b/third_party/iwlwifi/platform/task.h
@@ -11,7 +11,7 @@
 
 #include <zircon/types.h>
 
-#include "kernel.h"
+#include "third_party/iwlwifi/platform/kernel.h"
 
 #if defined(__cplusplus)
 extern "C" {
diff --git a/third_party/iwlwifi/platform/time.cc b/third_party/iwlwifi/platform/time.cc
index 2fe34a2..2970c3a 100644
--- a/third_party/iwlwifi/platform/time.cc
+++ b/third_party/iwlwifi/platform/time.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "time.h"
+#include "third_party/iwlwifi/platform/time.h"
 
 #include <lib/async/time.h>
 
diff --git a/third_party/iwlwifi/platform/time.h b/third_party/iwlwifi/platform/time.h
index 5332f3d..97d074d 100644
--- a/third_party/iwlwifi/platform/time.h
+++ b/third_party/iwlwifi/platform/time.h
@@ -7,7 +7,7 @@
 
 #include <zircon/types.h>
 
-#include "kernel.h"
+#include "third_party/iwlwifi/platform/kernel.h"
 
 #if defined(__cplusplus)
 extern "C" {
diff --git a/third_party/iwlwifi/platform/wlan-softmac-device.cc b/third_party/iwlwifi/platform/wlan-softmac-device.cc
new file mode 100644
index 0000000..b65684f
--- /dev/null
+++ b/third_party/iwlwifi/platform/wlan-softmac-device.cc
@@ -0,0 +1,205 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/platform/wlan-softmac-device.h"
+
+#include <zircon/assert.h>
+#include <zircon/status.h>
+
+#include <memory>
+
+extern "C" {
+#include "third_party/iwlwifi/mvm/mvm.h"
+}  // extern "C"
+
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/mvm-mlme.h"
+#include "third_party/iwlwifi/platform/mvm-sta.h"
+#include "third_party/iwlwifi/platform/scoped_utils.h"
+
+namespace wlan::iwlwifi {
+
+WlanSoftmacDevice::WlanSoftmacDevice(zx_device* parent, iwl_trans* drvdata, uint16_t iface_id,
+                                     struct iwl_mvm_vif* mvmvif)
+    : ddk::Device<WlanSoftmacDevice, ddk::Initializable, ddk::Unbindable>(parent),
+      mvmvif_(mvmvif),
+      drvdata_(drvdata),
+      iface_id_(iface_id) {}
+
+WlanSoftmacDevice::~WlanSoftmacDevice() = default;
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacQuery(uint32_t options, wlan_softmac_info_t* out_info) {
+  return mac_query(mvmvif_, options, out_info);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacStart(const wlan_softmac_ifc_protocol_t* ifc,
+                                                zx::channel* out_mlme_channel) {
+  return mac_start(mvmvif_, ifc, (zx_handle_t*)out_mlme_channel);
+}
+
+void WlanSoftmacDevice::WlanSoftmacStop() {
+  ap_mvm_sta_.reset();
+  mac_stop(mvmvif_);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacQueueTx(uint32_t options,
+                                                  const wlan_tx_packet_t* packet) {
+  if (ap_mvm_sta_ == nullptr) {
+    return ZX_ERR_BAD_STATE;
+  }
+
+  if (packet->mac_frame_size > WLAN_MSDU_MAX_LEN) {
+    IWL_ERR(mvmvif_, "Frame size is to large (%lu). expect less than %lu.\n",
+            packet->mac_frame_size, WLAN_MSDU_MAX_LEN);
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  ieee80211_mac_packet mac_packet = {};
+  mac_packet.common_header =
+      reinterpret_cast<const ieee80211_frame_header*>(packet->mac_frame_buffer);
+  mac_packet.header_size = ieee80211_get_header_len(mac_packet.common_header);
+  if (mac_packet.header_size > packet->mac_frame_size) {
+    IWL_ERR(mvmvif_, "TX packet header size %zu too large for data size %zu\n",
+            mac_packet.header_size, packet->mac_frame_size);
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  mac_packet.body = packet->mac_frame_buffer + mac_packet.header_size;
+  mac_packet.body_size = packet->mac_frame_size - mac_packet.header_size;
+  if (ieee80211_pkt_is_protected(mac_packet.common_header)) {
+    switch (ieee80211_get_frame_type(mac_packet.common_header)) {
+      case ieee80211_frame_type::IEEE80211_FRAME_TYPE_MGMT:
+        mac_packet.info.control.hw_key = ap_mvm_sta_->GetKey(WLAN_KEY_TYPE_IGTK);
+        break;
+      case ieee80211_frame_type::IEEE80211_FRAME_TYPE_DATA:
+        mac_packet.info.control.hw_key = ap_mvm_sta_->GetKey(WLAN_KEY_TYPE_PAIRWISE);
+        break;
+      default:
+        break;
+    }
+  }
+
+  auto lock = std::lock_guard(mvmvif_->mvm->mutex);
+  return iwl_mvm_mac_tx(mvmvif_, ap_mvm_sta_->iwl_mvm_sta(), &mac_packet);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacSetChannel(uint32_t options,
+                                                     const wlan_channel_t* channel) {
+  zx_status_t status = ZX_OK;
+
+  // If the AP sta already exists, it probably was left from the previous association attempt.
+  // Remove it first.
+  if (ap_mvm_sta_ != nullptr) {
+    if ((status = mac_unconfigure_bss(mvmvif_)) != ZX_OK) {
+      return status;
+    }
+    ap_mvm_sta_.reset();
+  }
+  return mac_set_channel(mvmvif_, options, channel);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacConfigureBss(uint32_t options,
+                                                       const bss_config_t* config) {
+  zx_status_t status = ZX_OK;
+  if (ap_mvm_sta_ != nullptr) {
+    return ZX_ERR_ALREADY_BOUND;
+  }
+  if ((status = mac_configure_bss(mvmvif_, options, config)) != ZX_OK) {
+    return status;
+  }
+
+  ZX_DEBUG_ASSERT(mvmvif_->mac_role == WLAN_MAC_ROLE_CLIENT);
+  std::unique_ptr<MvmSta> ap_mvm_sta;
+  if ((status = MvmSta::Create(mvmvif_, config->bssid, &ap_mvm_sta)) != ZX_OK) {
+    return status;
+  }
+
+  ap_mvm_sta_ = std::move(ap_mvm_sta);
+  return ZX_OK;
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacEnableBeaconing(uint32_t options,
+                                                          const wlan_bcn_config_t* bcn_cfg) {
+  return mac_enable_beaconing(mvmvif_, options, bcn_cfg);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacConfigureBeacon(uint32_t options,
+                                                          const wlan_tx_packet_t* pkt) {
+  return mac_configure_beacon(mvmvif_, options, pkt);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacSetKey(uint32_t options,
+                                                 const wlan_key_config_t* key_config) {
+  if (ap_mvm_sta_ == nullptr) {
+    return ZX_ERR_BAD_STATE;
+  }
+  return ap_mvm_sta_->SetKey(key_config);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacConfigureAssoc(uint32_t options,
+                                                         const wlan_assoc_ctx_t* assoc_ctx) {
+  zx_status_t status = ZX_OK;
+  if (ap_mvm_sta_ == nullptr) {
+    return ZX_ERR_BAD_STATE;
+  }
+  if ((status = ap_mvm_sta_->ChangeState(iwl_sta_state::IWL_STA_AUTHORIZED)) != ZX_OK) {
+    return status;
+  }
+  return mac_configure_assoc(mvmvif_, options, assoc_ctx);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacClearAssoc(
+    uint32_t options, const uint8_t peer_addr_list[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]) {
+  zx_status_t status = ZX_OK;
+
+  if (ap_mvm_sta_ == nullptr) {
+    return ZX_ERR_BAD_STATE;
+  }
+
+  // Mark the station is no longer associated. This must be set before we start operating on the STA
+  // instance.
+  mvmvif_->bss_conf.assoc = false;
+  ap_mvm_sta_.reset();
+
+  if ((status = mac_clear_assoc(mvmvif_, options, peer_addr_list)) != ZX_OK) {
+    return status;
+  }
+
+  return ZX_OK;
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacStartPassiveScan(
+    const wlan_softmac_passive_scan_args_t* passive_scan_args, uint64_t* out_scan_id) {
+  return mac_start_passive_scan(mvmvif_, passive_scan_args, out_scan_id);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacStartActiveScan(
+    const wlan_softmac_active_scan_args_t* active_scan_args, uint64_t* out_scan_id) {
+  return mac_start_active_scan(mvmvif_, active_scan_args, out_scan_id);
+}
+
+zx_status_t WlanSoftmacDevice::WlanSoftmacUpdateWmmParams(wlan_ac_t ac,
+                                                          const wlan_wmm_params_t* params) {
+  IWL_ERR(this, "%s() needs porting\n", __func__);
+  return ZX_ERR_NOT_SUPPORTED;
+}
+
+void WlanSoftmacDevice::DdkInit(ddk::InitTxn txn) {
+  txn.Reply(mac_init(mvmvif_, drvdata_, zxdev(), iface_id_));
+}
+
+void WlanSoftmacDevice::DdkRelease() {
+  IWL_DEBUG_INFO(this, "Releasing iwlwifi mac-device\n");
+  mac_release(mvmvif_);
+
+  delete this;
+}
+
+void WlanSoftmacDevice::DdkUnbind(ddk::UnbindTxn txn) {
+  IWL_DEBUG_INFO(this, "Unbinding iwlwifi mac-device\n");
+  mac_unbind(mvmvif_);
+  txn.Reply();
+}
+
+}  // namespace wlan::iwlwifi
diff --git a/third_party/iwlwifi/platform/wlan-softmac-device.h b/third_party/iwlwifi/platform/wlan-softmac-device.h
new file mode 100644
index 0000000..5e9eeab
--- /dev/null
+++ b/third_party/iwlwifi/platform/wlan-softmac-device.h
@@ -0,0 +1,71 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_WLAN_SOFTMAC_DEVICE_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_WLAN_SOFTMAC_DEVICE_H_
+
+#include <fuchsia/hardware/wlan/softmac/cpp/banjo.h>
+#include <fuchsia/wlan/ieee80211/c/banjo.h>
+#include <fuchsia/wlan/internal/cpp/banjo.h>
+#include <lib/ddk/device.h>
+
+#include <memory>
+
+#include <ddktl/device.h>
+
+struct iwl_mvm_vif;
+struct iwl_trans;
+
+namespace wlan::iwlwifi {
+
+class MvmSta;
+class WlanSoftmacDevice;
+
+class WlanSoftmacDevice
+    : public ddk::Device<WlanSoftmacDevice, ddk::Initializable, ddk::Unbindable>,
+      public ::ddk::WlanSoftmacProtocol<WlanSoftmacDevice, ::ddk::base_protocol> {
+ public:
+  WlanSoftmacDevice(zx_device* parent, iwl_trans* drvdata, uint16_t iface_id,
+                    struct iwl_mvm_vif* mvmvif);
+  ~WlanSoftmacDevice();
+
+  void DdkInit(ddk::InitTxn txn);
+  void DdkRelease();
+  void DdkUnbind(ddk::UnbindTxn txn);
+
+  // WlanSoftmac interface implementation.
+  zx_status_t WlanSoftmacQuery(uint32_t options, wlan_softmac_info_t* out_info);
+  zx_status_t WlanSoftmacStart(const wlan_softmac_ifc_protocol_t* ifc,
+                               zx::channel* out_mlme_channel);
+  void WlanSoftmacStop();
+  zx_status_t WlanSoftmacQueueTx(uint32_t options, const wlan_tx_packet_t* packet);
+  zx_status_t WlanSoftmacSetChannel(uint32_t options, const wlan_channel_t* channel);
+  zx_status_t WlanSoftmacConfigureBss(uint32_t options, const bss_config_t* config);
+  zx_status_t WlanSoftmacEnableBeaconing(uint32_t options, const wlan_bcn_config_t* bcn_cfg);
+  zx_status_t WlanSoftmacConfigureBeacon(uint32_t options, const wlan_tx_packet_t* pkt);
+  zx_status_t WlanSoftmacSetKey(uint32_t options, const wlan_key_config_t* key_config);
+  zx_status_t WlanSoftmacConfigureAssoc(uint32_t options, const wlan_assoc_ctx_t* assoc_ctx);
+  zx_status_t WlanSoftmacClearAssoc(
+      uint32_t options, const uint8_t peer_addr_list[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]);
+  zx_status_t WlanSoftmacStartPassiveScan(const wlan_softmac_passive_scan_args_t* passive_scan_args,
+                                          uint64_t* out_scan_id);
+  zx_status_t WlanSoftmacStartActiveScan(const wlan_softmac_active_scan_args_t* active_scan_args,
+                                         uint64_t* out_scan_id);
+  zx_status_t WlanSoftmacUpdateWmmParams(wlan_ac_t ac, const wlan_wmm_params_t* params);
+
+ protected:
+  struct iwl_mvm_vif* mvmvif_;
+
+ private:
+  iwl_trans* drvdata_;
+  uint16_t iface_id_;
+
+  // Each peer on this interface will require a MvmSta instance.  For now, as we only support client
+  // mode, we have only one peer (the AP), which simplifies things.
+  std::unique_ptr<MvmSta> ap_mvm_sta_;
+};
+
+}  // namespace wlan::iwlwifi
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_WLAN_SOFTMAC_DEVICE_H_
diff --git a/third_party/iwlwifi/platform/wlanmac-device.cc b/third_party/iwlwifi/platform/wlanmac-device.cc
deleted file mode 100644
index f035e23..0000000
--- a/third_party/iwlwifi/platform/wlanmac-device.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/iwlwifi/platform/wlanmac-device.h"
-
-#include <zircon/status.h>
-
-#include <memory>
-
-extern "C" {
-#include "third_party/iwlwifi/mvm/mvm.h"
-}  // extern "C"
-
-#include "third_party/iwlwifi/platform/mvm-mlme.h"
-
-namespace wlan::iwlwifi {
-
-zx_status_t WlanmacDevice::WlanmacQuery(uint32_t options, wlanmac_info_t* out_info) {
-  return mac_query(mvmvif_, options, out_info);
-}
-
-zx_status_t WlanmacDevice::WlanmacStart(const wlanmac_ifc_protocol_t* ifc,
-                                        zx::channel* out_mlme_channel) {
-  return mac_start(mvmvif_, ifc, (zx_handle_t*)out_mlme_channel);
-}
-
-void WlanmacDevice::WlanmacStop() { mac_stop(mvmvif_); }
-
-zx_status_t WlanmacDevice::WlanmacQueueTx(uint32_t options, const wlan_tx_packet_t* packet) {
-  return mac_queue_tx(mvmvif_, options, packet);
-}
-
-zx_status_t WlanmacDevice::WlanmacSetChannel(uint32_t options, const wlan_channel_t* channel) {
-  return mac_set_channel(mvmvif_, options, channel);
-}
-
-zx_status_t WlanmacDevice::WlanmacConfigureBss(uint32_t options, const bss_config_t* config) {
-  return mac_configure_bss(mvmvif_, options, config);
-}
-
-zx_status_t WlanmacDevice::WlanmacEnableBeaconing(uint32_t options,
-                                                  const wlan_bcn_config_t* bcn_cfg) {
-  return mac_enable_beaconing(mvmvif_, options, bcn_cfg);
-}
-
-zx_status_t WlanmacDevice::WlanmacConfigureBeacon(uint32_t options, const wlan_tx_packet_t* pkt) {
-  return mac_configure_beacon(mvmvif_, options, pkt);
-}
-
-zx_status_t WlanmacDevice::WlanmacSetKey(uint32_t options, const wlan_key_config_t* key_config) {
-  return mac_set_key(mvmvif_, options, key_config);
-}
-
-zx_status_t WlanmacDevice::WlanmacConfigureAssoc(uint32_t options,
-                                                 const wlan_assoc_ctx_t* assoc_ctx) {
-  return mac_configure_assoc(mvmvif_, options, assoc_ctx);
-}
-
-zx_status_t WlanmacDevice::WlanmacClearAssoc(
-    uint32_t options, const uint8_t peer_addr_list[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]) {
-  return mac_clear_assoc(mvmvif_, options, peer_addr_list);
-}
-
-zx_status_t WlanmacDevice::WlanmacStartHwScan(const wlan_hw_scan_config_t* scan_config) {
-  return mac_start_hw_scan(mvmvif_, scan_config);
-}
-
-zx_status_t WlanmacDevice::WlanmacUpdateWmmParams(wlan_ac_t ac, const wlan_wmm_params_t* params) {
-  IWL_ERR(this, "%s() needs porting\n", __func__);
-  return ZX_ERR_NOT_SUPPORTED;
-}
-
-void WlanmacDevice::DdkInit(ddk::InitTxn txn) {
-  txn.Reply(mac_init(mvmvif_, drvdata_, zxdev(), iface_id_));
-}
-
-void WlanmacDevice::DdkRelease() {
-  IWL_DEBUG_INFO(this, "Releasing iwlwifi mac-device\n");
-  mac_release(mvmvif_);
-
-  delete this;
-}
-
-void WlanmacDevice::DdkUnbind(ddk::UnbindTxn txn) {
-  IWL_DEBUG_INFO(this, "Unbinding iwlwifi mac-device\n");
-  mac_unbind(mvmvif_);
-  txn.Reply();
-}
-
-}  // namespace wlan::iwlwifi
diff --git a/third_party/iwlwifi/platform/wlanmac-device.h b/third_party/iwlwifi/platform/wlanmac-device.h
deleted file mode 100644
index 498228e..0000000
--- a/third_party/iwlwifi/platform/wlanmac-device.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <fuchsia/hardware/wlan/mac/cpp/banjo.h>
-#include <fuchsia/wlan/ieee80211/c/banjo.h>
-#include <fuchsia/wlan/internal/cpp/banjo.h>
-#include <lib/ddk/device.h>
-
-#include <ddktl/device.h>
-
-#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_WLANMAC_DEVICE_H_
-#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_WLANMAC_DEVICE_H_
-
-struct iwl_mvm_vif;
-struct iwl_trans;
-
-namespace wlan::iwlwifi {
-
-class WlanmacDevice;
-using WlanmacDeviceType = ddk::Device<WlanmacDevice, ddk::Initializable, ddk::Unbindable>;
-
-class WlanmacDevice : public WlanmacDeviceType,
-                      public ::ddk::WlanmacProtocol<WlanmacDevice, ::ddk::base_protocol> {
- public:
-  WlanmacDevice(zx_device* parent, iwl_trans* drvdata, uint16_t iface_id,
-                struct iwl_mvm_vif* mvmvif)
-      : WlanmacDeviceType(parent), mvmvif_(mvmvif), drvdata_(drvdata), iface_id_(iface_id){}
-  ~WlanmacDevice() = default;
-
-  void DdkInit(ddk::InitTxn txn);
-  void DdkRelease();
-  void DdkUnbind(ddk::UnbindTxn txn);
-
-  // Wlanmac interface implementation.
-  zx_status_t WlanmacQuery(uint32_t options, wlanmac_info_t* out_info);
-  zx_status_t WlanmacStart(const wlanmac_ifc_protocol_t* ifc, zx::channel* out_mlme_channel);
-  void WlanmacStop();
-  zx_status_t WlanmacQueueTx(uint32_t options, const wlan_tx_packet_t* packet);
-  zx_status_t WlanmacSetChannel(uint32_t options, const wlan_channel_t* channel);
-  zx_status_t WlanmacConfigureBss(uint32_t options, const bss_config_t* config);
-  zx_status_t WlanmacEnableBeaconing(uint32_t options, const wlan_bcn_config_t* bcn_cfg);
-  zx_status_t WlanmacConfigureBeacon(uint32_t options, const wlan_tx_packet_t* pkt);
-  zx_status_t WlanmacSetKey(uint32_t options, const wlan_key_config_t* key_config);
-  zx_status_t WlanmacConfigureAssoc(uint32_t options, const wlan_assoc_ctx_t* assoc_ctx);
-  zx_status_t WlanmacClearAssoc(uint32_t options,
-                                const uint8_t peer_addr_list[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]);
-  zx_status_t WlanmacStartHwScan(const wlan_hw_scan_config_t* scan_config);
-  zx_status_t WlanmacUpdateWmmParams(wlan_ac_t ac, const wlan_wmm_params_t* params);
-
- protected:
-  struct iwl_mvm_vif* mvmvif_;
-
- private:
-  iwl_trans* drvdata_;
-  uint16_t iface_id_;
-};
-
-}  // namespace wlan::iwlwifi
-
-#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_PLATFORM_WLANMAC_DEVICE_H_
diff --git a/third_party/iwlwifi/platform/wlanphy-impl-device.cc b/third_party/iwlwifi/platform/wlanphy-impl-device.cc
index d23adaa..51d575f 100644
--- a/third_party/iwlwifi/platform/wlanphy-impl-device.cc
+++ b/third_party/iwlwifi/platform/wlanphy-impl-device.cc
@@ -13,7 +13,7 @@
 }  // extern "C"
 
 #include "third_party/iwlwifi/platform/mvm-mlme.h"
-#include "third_party/iwlwifi/platform/wlanmac-device.h"
+#include "third_party/iwlwifi/platform/wlan-softmac-device.h"
 
 namespace wlan::iwlwifi {
 
@@ -24,8 +24,11 @@
 
 void WlanphyImplDevice::DdkRelease() { delete this; }
 
-zx_status_t WlanphyImplDevice::WlanphyImplQuery(wlanphy_impl_info_t* out_info) {
-  return phy_query(drvdata(), out_info);
+zx_status_t WlanphyImplDevice::WlanphyImplGetSupportedMacRoles(
+    wlan_mac_role_t out_supported_mac_roles_list[fuchsia_wlan_common_MAX_SUPPORTED_MAC_ROLES],
+    uint8_t* out_supported_mac_roles_count) {
+  return phy_get_supported_mac_roles(drvdata(), out_supported_mac_roles_list,
+                                     out_supported_mac_roles_count);
 }
 
 zx_status_t WlanphyImplDevice::WlanphyImplCreateIface(const wlanphy_impl_create_iface_req_t* req,
@@ -45,15 +48,15 @@
   struct iwl_mvm* mvm = iwl_trans_get_mvm(drvdata());
   struct iwl_mvm_vif* mvmvif = mvm->mvmvif[*out_iface_id];
 
-  auto wlanmac_device =
-      std::make_unique<WlanmacDevice>(zxdev(), drvdata(), *out_iface_id, mvmvif);
+  auto wlan_softmac_device =
+      std::make_unique<WlanSoftmacDevice>(parent(), drvdata(), *out_iface_id, mvmvif);
 
-  if ((status = wlanmac_device->DdkAdd("iwlwifi-wlanmac")) != ZX_OK) {
+  if ((status = wlan_softmac_device->DdkAdd("iwlwifi-wlan-softmac")) != ZX_OK) {
     IWL_ERR(this, "%s() failed mac device add: %s\n", __func__, zx_status_get_string(status));
     phy_create_iface_undo(drvdata(), *out_iface_id);
     return status;
   }
-  wlanmac_device.release();
+  wlan_softmac_device.release();
   return ZX_OK;
 }
 
diff --git a/third_party/iwlwifi/platform/wlanphy-impl-device.h b/third_party/iwlwifi/platform/wlanphy-impl-device.h
index ce390e6..fe4c8be 100644
--- a/third_party/iwlwifi/platform/wlanphy-impl-device.h
+++ b/third_party/iwlwifi/platform/wlanphy-impl-device.h
@@ -34,7 +34,9 @@
   virtual const iwl_trans* drvdata() const = 0;
 
   // WlanphyImpl interface implementation.
-  zx_status_t WlanphyImplQuery(wlanphy_impl_info_t* out_info);
+  zx_status_t WlanphyImplGetSupportedMacRoles(
+      wlan_mac_role_t out_supported_mac_roles_list[fuchsia_wlan_common_MAX_SUPPORTED_MAC_ROLES],
+      uint8_t* out_supported_mac_roles_count);
   zx_status_t WlanphyImplCreateIface(const wlanphy_impl_create_iface_req_t* req,
                                      uint16_t* out_iface_id);
   zx_status_t WlanphyImplDestroyIface(uint16_t iface_id);
diff --git a/third_party/iwlwifi/test/BUILD.gn b/third_party/iwlwifi/test/BUILD.gn
index 09adbe7..5758c53 100644
--- a/third_party/iwlwifi/test/BUILD.gn
+++ b/third_party/iwlwifi/test/BUILD.gn
@@ -13,12 +13,14 @@
 source_set("sim_library") {
   testonly = true
   sources = [
-    "fake-ucode-capa-test.cc",
+    "fake-ucode-test.cc",
     "inspect-host-cmd.cc",
     "inspect-host-cmd.h",
+    "sim-mcc-update.cc",
     "sim-mvm.cc",
     "sim-nvm-data.inc",
     "sim-nvm.cc",
+    "sim-time-event.cc",
     "sim-trans.cc",
     "single-ap-test.cc",
     "tlv-fw-builder.cc",
@@ -26,43 +28,40 @@
     "wlan-pkt-builder.cc",
   ]
   public = [
-    "fake-ucode-capa-test.h",
+    "fake-ucode-test.h",
     "mock-trans.h",
+    "sim-mcc-update.h",
     "sim-mvm.h",
     "sim-nvm.h",
+    "sim-time-event.h",
     "sim-trans.h",
     "sim.h",
     "single-ap-test.h",
     "wlan-pkt-builder.h",
   ]
   deps = [
-    "//sdk/banjo/fuchsia.hardware.wlan.mac:fuchsia.hardware.wlan.mac_banjo_cpp",
+    "//sdk/banjo/fuchsia.hardware.wlan.softmac:fuchsia.hardware.wlan.softmac_banjo_cpp",
     "//sdk/banjo/fuchsia.hardware.wlanphyimpl:fuchsia.hardware.wlanphyimpl_banjo_cpp",
     "//sdk/fidl/fuchsia.wlan.common:fuchsia.wlan.common_banjo_cpp",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/fw:api",
-    "//src/iwlwifi/mvm",
-    "//src/iwlwifi/platform:fuchsia_device",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw:api",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:fuchsia_device",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:rcu_manager",
     "//src/devices/testing/fake-bti",
     "//zircon/system/ulib/async-loop:async-loop-cpp",
     "//zircon/system/ulib/async-loop:async-loop-default",
-    "//zircon/system/ulib/fbl",
   ]
   public_deps = [
-    "//src/connectivity/wlan/drivers/testing/lib/sim-device",
-    "//src/connectivity/wlan/drivers/testing/lib/sim-env",
-    "//src/connectivity/wlan/drivers/testing/lib/sim-fake-ap",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
+    "//src/connectivity/wlan/lib/common/cpp:common",
     "//src/devices/pci/testing:pci-protocol-fake",
     "//src/devices/testing/mock-ddk",
     "//zircon/system/public",
     "//zircon/system/ulib/zxtest",
   ]
   public_configs = [ ":test_config" ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 # Stub entrypoints for MVM.
@@ -71,7 +70,7 @@
   sources = [ "stub-mvm.cc" ]
   deps = [ "//zircon/system/public" ]
   public_configs = [
-    "//src/iwlwifi:fuchsia_config",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:fuchsia_config",
   ]
 }
 
@@ -81,7 +80,7 @@
   testonly = true
   sources = [ "driver-inspector-test.cc" ]
   deps = [
-    "//src/iwlwifi/platform:driver_inspector",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:driver_inspector",
     "//zircon/system/ulib/inspect",
     "//zircon/system/ulib/zxtest",
   ]
@@ -96,9 +95,6 @@
     ":sim_library",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("fw_test") {
@@ -107,13 +103,10 @@
   sources = [ "fw-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("fw_dbg_test") {
@@ -122,14 +115,11 @@
   sources = [ "fw-dbg-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi/fw",
-    "//src/iwlwifi/platform:driver_inspector",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:driver_inspector",
     "//zircon/system/ulib/inspect",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("iwl_phy_db_test") {
@@ -138,7 +128,7 @@
   sources = [ "iwl-phy-db-test.cc" ]
   deps = [
     ":stub_mvm",
-    "//src/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
     "//src/devices/testing/no_ddk",
     "//zircon/system/ulib/zxtest",
   ]
@@ -150,14 +140,10 @@
   sources = [ "mac80211-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi/mvm",
-    "//zircon/system/ulib/fbl",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//zircon/system/ulib/mock-function",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("mac_ctxt_test") {
@@ -166,32 +152,9 @@
   sources = [ "mac-ctxt-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi/mvm",
-    "//zircon/system/ulib/zircon-internal",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
-}
-
-executable("mvm_mlme_test") {
-  output_name = "mvm_mlme_test"
-  testonly = true
-  sources = [ "mvm-mlme-test.cc" ]
-  deps = [
-    ":sim_library",
-    "//sdk/fidl/fuchsia.wlan.common:fuchsia.wlan.common_banjo_cpp",
-    "//sdk/fidl/fuchsia.wlan.internal:fuchsia.wlan.internal_banjo_cpp",
-    "//src/iwlwifi/mvm",
-    "//src/iwlwifi/platform:fuchsia_device",
-    "//zircon/system/public",
-    "//zircon/system/ulib/mock-function",
-    "//zircon/system/ulib/zxtest",
-  ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 # Test code for MVM features.
@@ -201,15 +164,11 @@
   sources = [ "mvm-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//src/devices/testing/fake-bti",
     "//zircon/system/ulib/mock-function",
-    "//zircon/system/ulib/zircon-internal",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("notif_wait_test") {
@@ -218,7 +177,7 @@
   sources = [ "notif-wait-test.cc" ]
   deps = [
     ":stub_mvm",
-    "//src/iwlwifi/fw",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw",
     "//src/devices/testing/no_ddk",
     "//zircon/system/ulib/zxtest",
   ]
@@ -230,13 +189,10 @@
   sources = [ "nvm-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("pcie_test") {
@@ -245,10 +201,10 @@
   sources = [ "pcie-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi:core",
-    "//src/iwlwifi/fw:api",
-    "//src/iwlwifi/pcie",
-    "//src/iwlwifi/platform:fuchsia_device",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/fw:api",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/pcie",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:fuchsia_device",
     "//src/devices/pci/testing:pci-protocol-fake",
     "//src/devices/testing/fake-bti",
     "//src/devices/testing/mock-ddk",
@@ -257,13 +213,9 @@
     "//zircon/system/ulib/async-loop:async-loop-default",
     "//zircon/system/ulib/mock-function",
     "//zircon/system/ulib/sync",
-    "//zircon/system/ulib/zircon-internal",
     "//zircon/system/ulib/zx",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("phy_ctxt_test") {
@@ -272,13 +224,9 @@
   sources = [ "phy-ctxt-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi/mvm",
-    "//zircon/system/ulib/zircon-internal",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("platform_test") {
@@ -287,25 +235,34 @@
   sources = [ "platform-test.cc" ]
   deps = [
     ":stub_mvm",
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//src/devices/testing/no_ddk",
     "//zircon/system/ulib/zxtest",
   ]
 }
 
+executable("rcu_manager_test") {
+  output_name = "rcu_manager_test"
+  testonly = true
+  sources = [ "rcu-manager-test.cc" ]
+  deps = [
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:rcu_manager",
+    "//zircon/system/ulib/async-testing",
+    "//zircon/system/ulib/sync",
+    "//zircon/system/ulib/zxtest",
+  ]
+}
+
 executable("sta_test") {
   output_name = "sta_test"
   testonly = true
   sources = [ "sta-test.cc" ]
   deps = [
     ":sim_library",
-    "//src/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//zircon/system/ulib/mock-function",
     "//zircon/system/ulib/zxtest",
   ]
-
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
 }
 
 executable("task_test") {
@@ -314,7 +271,7 @@
   sources = [ "task-test.cc" ]
   deps = [
     ":stub_mvm",
-    "//src/iwlwifi/platform",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform",
     "//src/devices/testing/no_ddk",
     "//zircon/system/public",
     "//zircon/system/ulib/async-testing",
@@ -327,13 +284,45 @@
   testonly = true
   sources = [ "utils-test.cc" ]
   deps = [
-    "//src/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
     "//src/devices/testing/no_ddk",
     "//zircon/system/ulib/zxtest",
   ]
+}
 
-  # TODO(https://fxbug.dev/58162): delete the below and fix compiler warnings
-  configs += [ "//build/config:Wno-conversion" ]
+executable("wlan_softmac_device_test") {
+  output_name = "wlan_softmac_device_test"
+  testonly = true
+  sources = [ "wlan-softmac-device-test.cc" ]
+  deps = [
+    ":sim_library",
+    "//sdk/banjo/fuchsia.hardware.wlan.associnfo:fuchsia.hardware.wlan.associnfo_banjo_cpp",
+    "//sdk/banjo/fuchsia.hardware.wlan.softmac:fuchsia.hardware.wlan.softmac_banjo_cpp",
+    "//sdk/fidl/fuchsia.wlan.ieee80211:fuchsia.wlan.ieee80211_llcpp",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi:core",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:fuchsia_device",
+    "//zircon/system/public",
+    "//zircon/system/ulib/mock-function",
+    "//zircon/system/ulib/mock-function",
+    "//zircon/system/ulib/zx",
+    "//zircon/system/ulib/zxtest",
+  ]
+}
+
+executable("wlanphy_impl_device_test") {
+  output_name = "wlanphy_impl_device_test"
+  testonly = true
+  sources = [ "wlanphy-impl-device-test.cc" ]
+  deps = [
+    ":sim_library",
+    "//sdk/fidl/fuchsia.wlan.common:fuchsia.wlan.common_banjo_cpp",
+    "//sdk/fidl/fuchsia.wlan.internal:fuchsia.wlan.internal_banjo_cpp",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/mvm",
+    "//src/connectivity/wlan/drivers/third_party/intel/iwlwifi/platform:fuchsia_device",
+    "//zircon/system/public",
+    "//zircon/system/ulib/zxtest",
+  ]
 }
 
 _tests = [
@@ -344,16 +333,18 @@
   "iwl_phy_db_test",
   "mac80211_test",
   "mac_ctxt_test",
-  "mvm_mlme_test",
   "mvm_test",
   "notif_wait_test",
   "nvm_test",
   "pcie_test",
   "phy_ctxt_test",
   "platform_test",
+  "rcu_manager_test",
   "sta_test",
   "task_test",
   "utils_test",
+  "wlan_softmac_device_test",
+  "wlanphy_impl_device_test",
 ]
 
 foreach(test_name, _tests) {
diff --git a/third_party/iwlwifi/test/fake-ucode-capa-test.h b/third_party/iwlwifi/test/fake-ucode-capa-test.h
deleted file mode 100644
index 9468785..0000000
--- a/third_party/iwlwifi/test/fake-ucode-capa-test.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_FAKE_UCODE_CAPA_TEST_H_
-#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_FAKE_UCODE_CAPA_TEST_H_
-
-#include <memory>
-
-#include <zxtest/zxtest.h>
-
-#include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-env.h"
-#include "third_party/iwlwifi/test/sim-trans.h"
-#include "src/devices/testing/mock-ddk/mock-device.h"
-
-namespace wlan::testing {
-
-// Helper class for unit test code to inherit in order to create an fake firmware with
-// customized ucode capability.
-//
-// Note that the contructor will call the init function of transportation layer,
-// and assert it is successful. The test case doesn't need to init again.
-//
-class FakeUcodeCapaTest : public ::zxtest::Test {
- public:
-  // The constructor takes two parameters, they will be assigned to the two fields struct
-  // iwl_ucode_capa, api_index indicates the offset of api_capa in iwl_ucode_capabilities._capa, the
-  // value of api_index is usually 0 for 8265. api_capa is the 4-byte flag indicates the supported
-  // ucode capabilities of this firmware.
-  //
-  // For how iwlwifi driver is parsing the ucode capabilities from firmware tlv binary, please refer
-  // to iwl_set_ucode_capabilities() in iwlwifi/iwl-drv.c. For the value options of api_capa flags,
-  // please refer to enum iwl_ucode_tlv_capa in iwlwifi/fw/file.h.
-  FakeUcodeCapaTest(uint32_t api_index, uint32_t api_capa);
-  ~FakeUcodeCapaTest() = default;
-
- protected:
-  ::wlan::simulation::Environment dummy_env_;
-  std::shared_ptr<MockDevice> fake_parent_;
-  SimTransport sim_trans_;
-};
-
-}  // namespace wlan::testing
-
-#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_FAKE_UCODE_CAPA_TEST_H_
diff --git a/third_party/iwlwifi/test/fake-ucode-capa-test.cc b/third_party/iwlwifi/test/fake-ucode-test.cc
similarity index 74%
rename from third_party/iwlwifi/test/fake-ucode-capa-test.cc
rename to third_party/iwlwifi/test/fake-ucode-test.cc
index 77af2e2..ede2857 100644
--- a/third_party/iwlwifi/test/fake-ucode-capa-test.cc
+++ b/third_party/iwlwifi/test/fake-ucode-test.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/iwlwifi/test/fake-ucode-capa-test.h"
+#include "third_party/iwlwifi/test/fake-ucode-test.h"
 
 #include <zircon/assert.h>
 #include <zircon/status.h>
@@ -15,8 +15,9 @@
 
 namespace wlan::testing {
 
-FakeUcodeCapaTest::FakeUcodeCapaTest(uint32_t api_index, uint32_t api_capa)
-    : fake_parent_(MockDevice::FakeRootParent()), sim_trans_(&dummy_env_, fake_parent_.get()) {
+FakeUcodeTest::FakeUcodeTest(uint32_t capa_index, uint32_t capa_val, uint32_t api_index,
+                             uint32_t api_val)
+    : fake_parent_(MockDevice::FakeRootParent()), sim_trans_(fake_parent_.get()) {
   // Add a default MVM firmware to the fake DDK.
   TlvFwBuilder fw_builder;
 
@@ -27,10 +28,14 @@
   fw_builder.AddValue(IWL_UCODE_TLV_INIT, &dummy_ucode, sizeof(dummy_ucode));
   fw_builder.AddValue(IWL_UCODE_TLV_INIT_DATA, &dummy_ucode, sizeof(dummy_ucode));
 
-  const struct iwl_ucode_capa ucode_capa = {.api_index = cpu_to_le32(api_index),
-                                            .api_capa = cpu_to_le32(api_capa)};
+  const struct iwl_ucode_capa ucode_capa = {.api_index = cpu_to_le32(capa_index),
+                                            .api_capa = cpu_to_le32(capa_val)};
   fw_builder.AddValue(IWL_UCODE_TLV_ENABLED_CAPABILITIES, &ucode_capa, sizeof(ucode_capa));
 
+  const struct iwl_ucode_api ucode_api = {.api_index = cpu_to_le32(api_index),
+                                          .api_flags = cpu_to_le32(api_val)};
+  fw_builder.AddValue(IWL_UCODE_TLV_API_CHANGES_SET, &ucode_api, sizeof(ucode_api));
+
   const uint32_t ucode_phy_sku =
       cpu_to_le32((3 << FW_PHY_CFG_TX_CHAIN_POS) |  // Tx antenna 1 and 0.
                   (6 << FW_PHY_CFG_RX_CHAIN_POS));  // Rx antenna 2 and 1.
diff --git a/third_party/iwlwifi/test/fake-ucode-test.h b/third_party/iwlwifi/test/fake-ucode-test.h
new file mode 100644
index 0000000..73dbda7
--- /dev/null
+++ b/third_party/iwlwifi/test/fake-ucode-test.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_FAKE_UCODE_TEST_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_FAKE_UCODE_TEST_H_
+
+#include <memory>
+
+#include <zxtest/zxtest.h>
+
+#include "third_party/iwlwifi/test/sim-trans.h"
+#include "src/devices/testing/mock-ddk/mock-device.h"
+
+namespace wlan::testing {
+
+// Helper class for unit test code to inherit in order to create an fake firmware with
+// customized ucode capability.
+//
+// Note that the contructor will call the init function of transportation layer,
+// and assert it is successful. The test case doesn't need to init again.
+//
+class FakeUcodeTest : public ::zxtest::Test {
+ public:
+  // The constructor takes four parameters. They will be assigned to the API flags and capabilities.
+  // The *_index are the array index in iwl_ucode_capabilities._api[] and ._capa[]. The *_value are
+  // limited from 0 to 31. These 2 fields are used to present all values of IWL_UCODE_TLV_API_* and
+  // IWL_UCODE_TLV_CAPA_*.
+  //
+  // TODO(fxbug.dev/92106): support multiple bits.
+  //
+  // For how iwlwifi driver is parsing the ucode capabilities from firmware tlv binary, please refer
+  // to iwl_set_ucode_capabilities() in iwlwifi/iwl-drv.c. For the value options, please refer to
+  // enum iwl_ucode_tlv_capa and enum iwl_ucode_tlv_api in iwlwifi/fw/file.h.
+  FakeUcodeTest(uint32_t capa_index, uint32_t capa_val, uint32_t api_index, uint32_t api_val);
+  ~FakeUcodeTest() = default;
+
+ protected:
+  std::shared_ptr<MockDevice> fake_parent_;
+  SimTransport sim_trans_;
+};
+
+}  // namespace wlan::testing
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_FAKE_UCODE_TEST_H_
diff --git a/third_party/iwlwifi/test/fw-test.cc b/third_party/iwlwifi/test/fw-test.cc
index 4cb7e11..044ae11 100644
--- a/third_party/iwlwifi/test/fw-test.cc
+++ b/third_party/iwlwifi/test/fw-test.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <iterator>
+
 #include <zxtest/zxtest.h>
 
 extern "C" {
@@ -41,7 +43,7 @@
       FW_PAGING_SIZE * (NUM_OF_PAGE_PER_GROUP * (NUM_OF_PAGE_BLK - 1) + NUM_OF_PAGES_IN_LAST_BLK);
   uint8_t arbitrary_data[PAGING_MEM_SIZE] = {};
   for (size_t i = 0; i < sizeof(arbitrary_data); i++) {
-    arbitrary_data[i] = i;  // fill with arbitrary data for testing.
+    arbitrary_data[i] = static_cast<uint8_t>(i);  // fill with arbitrary data for testing.
   }
 
   // About the sections, see iwl_fill_paging_mem().
@@ -84,7 +86,7 @@
               // IWL_UCODE_INIT
               {
                   .sec = sec,
-                  .num_sec = ARRAY_SIZE(sec),
+                  .num_sec = std::size(sec),
                   .paging_mem_size = PAGING_MEM_SIZE,
               },
 
diff --git a/third_party/iwlwifi/test/inspect-host-cmd.cc b/third_party/iwlwifi/test/inspect-host-cmd.cc
index 661a92b..6eee771 100644
--- a/third_party/iwlwifi/test/inspect-host-cmd.cc
+++ b/third_party/iwlwifi/test/inspect-host-cmd.cc
@@ -10,11 +10,11 @@
 #include <stdio.h>
 #include <zircon/syscalls.h>
 
+#include <cstdio>
+#include <iterator>
 #include <sstream>
 #include <string>
 
-#include <fbl/string_printf.h>
-
 #define INSPECT_BUFSIZ 512
 
 // Given a 'bitmap_value' and an array of bit definition, this function returns a joined string
@@ -50,19 +50,18 @@
       "dup",
   };
 
-  std::string out(
-      fbl::StringPrintf("host_cmd id[0x%02x] flags[0x%x:%s]\n", cmd->id, cmd->flags,
-                        join_bitmap_string(flags_defs, ARRAY_SIZE(flags_defs), cmd->flags).c_str())
-          .c_str());
+  constexpr size_t kMaxStrBufSize = 1024;
+  char strbuf[kMaxStrBufSize];
+  std::snprintf(strbuf, kMaxStrBufSize, "host_cmd id[0x%02x] flags[0x%x:%s]\n", cmd->id, cmd->flags,
+                join_bitmap_string(flags_defs, std::size(flags_defs), cmd->flags).c_str());
+  std::string out(strbuf);
 
-  for (size_t i = 0; i < ARRAY_SIZE(cmd->len); i++) {
-    out +=
-        std::string(fbl::StringPrintf("  [%zu] dataflags[0x%x:%s] len[%d]\n", i, cmd->dataflags[i],
-                                      join_bitmap_string(dataflags_defs, ARRAY_SIZE(dataflags_defs),
-                                                         cmd->dataflags[i])
-                                          .c_str(),
-                                      cmd->len[i])
-                        .c_str());
+  for (size_t i = 0; i < std::size(cmd->len); i++) {
+    std::snprintf(
+        strbuf, kMaxStrBufSize, "  [%zu] dataflags[0x%x:%s] len[%d]\n", i, cmd->dataflags[i],
+        join_bitmap_string(dataflags_defs, std::size(dataflags_defs), cmd->dataflags[i]).c_str(),
+        cmd->len[i]);
+    out += std::string(strbuf);
   }
 
   return out;
diff --git a/third_party/iwlwifi/test/mac-ctxt-test.cc b/third_party/iwlwifi/test/mac-ctxt-test.cc
index 279eb23..d5ae86a 100644
--- a/third_party/iwlwifi/test/mac-ctxt-test.cc
+++ b/third_party/iwlwifi/test/mac-ctxt-test.cc
@@ -4,7 +4,7 @@
 
 // Used to test mvm/mac-ctxt.c
 
-#include <lib/zircon-internal/thread_annotations.h>
+#include <zircon/compiler.h>
 
 #include <zxtest/zxtest.h>
 
@@ -19,11 +19,11 @@
 
 class MacContextTest : public SingleApTest {
  public:
-  MacContextTest() TA_NO_THREAD_SAFETY_ANALYSIS {
+  MacContextTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
     mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans());
     mtx_lock(&mvm_->mutex);
   }
-  ~MacContextTest() TA_NO_THREAD_SAFETY_ANALYSIS { mtx_unlock(&mvm_->mutex); }
+  ~MacContextTest() __TA_NO_THREAD_SAFETY_ANALYSIS { mtx_unlock(&mvm_->mutex); }
 
  protected:
   struct iwl_mvm* mvm_;
@@ -32,7 +32,7 @@
 TEST_F(MacContextTest, Init) {
   struct iwl_mvm_vif mvmvif = {
       .mvm = mvm_,
-      .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
+      .mac_role = WLAN_MAC_ROLE_CLIENT,
   };
 
   ASSERT_OK(iwl_mvm_mac_ctxt_init(&mvmvif));
@@ -41,7 +41,7 @@
 TEST_F(MacContextTest, AddModifyRemove) {
   struct iwl_mvm_vif mvmvif = {
       .mvm = mvm_,
-      .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
+      .mac_role = WLAN_MAC_ROLE_CLIENT,
   };
 
   ASSERT_OK(iwl_mvm_mac_ctxt_init(&mvmvif));
diff --git a/third_party/iwlwifi/test/mac80211-test.cc b/third_party/iwlwifi/test/mac80211-test.cc
index d0e5b58..9145b6a 100644
--- a/third_party/iwlwifi/test/mac80211-test.cc
+++ b/third_party/iwlwifi/test/mac80211-test.cc
@@ -5,6 +5,9 @@
 // Used to test mvm/mac80211.c
 
 #include <lib/mock-function/mock-function.h>
+#include <zircon/compiler.h>
+
+#include <iterator>
 
 #include <zxtest/zxtest.h>
 
@@ -12,25 +15,19 @@
 #include "third_party/iwlwifi/mvm/mvm.h"
 }
 
+#include "third_party/iwlwifi/test/fake-ucode-test.h"
 #include "third_party/iwlwifi/test/mock-trans.h"
 #include "third_party/iwlwifi/test/single-ap-test.h"
-#include "zircon/system/ulib/fbl/include/fbl/auto_lock.h"
 
 namespace wlan::testing {
 namespace {
 
-class Mac80211Test : public SingleApTest {
+class ClientInterfaceHelper {
  public:
-  Mac80211Test() { mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans()); }
-  ~Mac80211Test() {
-    mtx_lock(&mvm_->mutex);
-    iwl_mvm_unbind_mvmvif(mvm_, mvmvif_idx_);
-    mtx_unlock(&mvm_->mutex);
-  }
+  ClientInterfaceHelper(SimTransport* sim_trans) : mvmvif_{}, ap_sta_{}, txqs_ {}
+  {
+    mvm_ = iwl_trans_get_mvm(sim_trans->iwl_trans());
 
- protected:
-  // A helper function to create a client interface to be used in the test case.
-  void ClientInterfaceHelper() {
     // First find a free slot for the interface.
     mtx_lock(&mvm_->mutex);
     EXPECT_EQ(ZX_OK, iwl_mvm_find_free_mvmvif_slot(mvm_, &mvmvif_idx_));
@@ -39,7 +36,7 @@
 
     // Initialize the interface data and add it to the mvm.
     mvmvif_.mvm = mvm_;
-    mvmvif_.mac_role = WLAN_INFO_MAC_ROLE_CLIENT;
+    mvmvif_.mac_role = WLAN_MAC_ROLE_CLIENT;
     mvmvif_.bss_conf.beacon_int = 16;
     iwl_mvm_mac_add_interface(&mvmvif_);
 
@@ -53,16 +50,30 @@
     mvmvif_.phy_ctxt = &mvm_->phy_ctxts[phy_ctxt_id];
 
     // Assign the AP sta info.
-    ASSERT_EQ(IEEE80211_TIDS_MAX + 1, ARRAY_SIZE(ap_sta_.txq));
-    for (size_t i = 0; i < ARRAY_SIZE(ap_sta_.txq); i++) {
+    ASSERT_EQ(IEEE80211_TIDS_MAX + 1, std::size(ap_sta_.txq));
+    for (size_t i = 0; i < std::size(ap_sta_.txq); i++) {
       ap_sta_.txq[i] = &txqs_[i];
     }
     ASSERT_EQ(ZX_OK, iwl_mvm_mac_sta_state(&mvmvif_, &ap_sta_, IWL_STA_NOTEXIST, IWL_STA_NONE));
 
     // Set it to associated.
-    mvm_->fw_id_to_mac_id[0]->sta_state = IWL_STA_AUTHORIZED;
+    mvmvif_.bss_conf.assoc = true;
   }
 
+  ~ClientInterfaceHelper() {
+    mtx_lock(&mvm_->mutex);
+    iwl_mvm_unbind_mvmvif(mvm_, mvmvif_idx_);
+    mtx_unlock(&mvm_->mutex);
+  }
+
+  struct iwl_mvm* mvm() {
+    return mvm_;
+  }
+  struct iwl_mvm_vif* mvmvif() {
+    return &mvmvif_;
+  }
+
+ private:
   struct iwl_mvm* mvm_;
   // for ClientInterfaceHelper().
   struct iwl_mvm_vif mvmvif_;
@@ -71,11 +82,32 @@
   struct iwl_mvm_txq txqs_[IEEE80211_TIDS_MAX + 1];
 };
 
+class Mac80211Test : public SingleApTest {
+ public:
+  Mac80211Test() : mvm_(iwl_trans_get_mvm(sim_trans_.iwl_trans())) {}
+  ~Mac80211Test() {}
+
+ protected:
+  struct iwl_mvm* mvm_;
+};
+
+class Mac80211UcodeTest : public FakeUcodeTest {
+ public:
+  Mac80211UcodeTest()
+      : FakeUcodeTest(0, BIT(IWL_UCODE_TLV_CAPA_LAR_SUPPORT), 0,
+                      BIT(IWL_UCODE_TLV_API_WIFI_MCC_UPDATE)),
+        mvm_(iwl_trans_get_mvm(sim_trans_.iwl_trans())) {}
+  ~Mac80211UcodeTest() {}
+
+ protected:
+  struct iwl_mvm* mvm_;
+};
+
 // Normal case: add an interface, then delete it.
 TEST_F(Mac80211Test, AddThenRemove) {
   struct iwl_mvm_vif mvmvif = {
       .mvm = mvm_,
-      .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
+      .mac_role = WLAN_MAC_ROLE_CLIENT,
   };
 
   ASSERT_OK(iwl_mvm_mac_add_interface(&mvmvif));
@@ -100,19 +132,19 @@
   struct iwl_mvm_vif mvmvif[] = {
       {
           .mvm = mvm_,
-          .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
+          .mac_role = WLAN_MAC_ROLE_CLIENT,
       },
       {
           .mvm = mvm_,
-          .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
+          .mac_role = WLAN_MAC_ROLE_CLIENT,
       },
       {
           .mvm = mvm_,
-          .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
+          .mac_role = WLAN_MAC_ROLE_CLIENT,
       },
   };
 
-  size_t mvmvif_count = ARRAY_SIZE(mvmvif);
+  size_t mvmvif_count = std::size(mvmvif);
   for (size_t i = 0; i < mvmvif_count; ++i) {
     ASSERT_OK(iwl_mvm_mac_add_interface(&mvmvif[i]));
 
@@ -178,13 +210,13 @@
 
 // Test the normal usage.
 //
-TEST_F(Mac80211Test, MvmSlotNormalCase) {
-  fbl::AutoLock auto_lock(&mvm_->mutex);
+TEST_F(Mac80211Test, MvmSlotNormalCase) __TA_NO_THREAD_SAFETY_ANALYSIS {
   int index;
   struct iwl_mvm_vif mvmvif = {};  // an instance to bind
   zx_status_t ret;
 
   // Fill up all mvmvif slots and expect okay
+  mtx_lock(&mvm_->mutex);
   for (size_t i = 0; i < MAX_NUM_MVMVIF; i++) {
     ret = iwl_mvm_find_free_mvmvif_slot(mvm_, &index);
     ASSERT_EQ(ret, ZX_OK);
@@ -202,12 +234,12 @@
   // One more is not accepted.
   ret = iwl_mvm_find_free_mvmvif_slot(mvm_, &index);
   ASSERT_EQ(ret, ZX_ERR_NO_RESOURCES);
+  mtx_unlock(&mvm_->mutex);
 }
 
 // Bind / unbind test.
 //
-TEST_F(Mac80211Test, MvmSlotBindUnbind) {
-  fbl::AutoLock auto_lock(&mvm_->mutex);
+TEST_F(Mac80211Test, MvmSlotBindUnbind) __TA_NO_THREAD_SAFETY_ANALYSIS {
   int index;
   struct iwl_mvm_vif mvmvif = {};  // an instance to bind
   zx_status_t ret;
@@ -216,6 +248,7 @@
   ASSERT_EQ(MAX_NUM_MVMVIF, 4);
 
   // First occupy the index 0, 1, 3.
+  mtx_lock(&mvm_->mutex);
   ret = iwl_mvm_bind_mvmvif(mvm_, 0, &mvmvif);
   ASSERT_EQ(ret, ZX_OK);
   ret = iwl_mvm_bind_mvmvif(mvm_, 1, &mvmvif);
@@ -241,12 +274,15 @@
   ret = iwl_mvm_find_free_mvmvif_slot(mvm_, &index);
   ASSERT_EQ(ret, ZX_OK);
   ASSERT_EQ(index, 1);
+  mtx_unlock(&mvm_->mutex);
 }
 
-class McastFilterTest : public Mac80211Test, public MockTrans {
+class McastFilterTestWithoutIface : public Mac80211Test, public MockTrans {
  public:
-  McastFilterTest() { BIND_TEST(mvm_->trans); }
-  ~McastFilterTest() { mock_send_cmd_.VerifyAndClear(); }
+  McastFilterTestWithoutIface() : mvm_(iwl_trans_get_mvm(sim_trans_.iwl_trans())) {
+    BIND_TEST(mvm_->trans);
+  }
+  ~McastFilterTestWithoutIface() { mock_send_cmd_.VerifyAndClear(); }
 
   // The values we expect.  We only test few arbitrary bytes in 'addr_list' .
   //
@@ -264,17 +300,27 @@
   static zx_status_t send_cmd_wrapper(struct iwl_trans* trans, struct iwl_host_cmd* hcmd) {
     auto mcast_cmd = reinterpret_cast<const struct iwl_mcast_filter_cmd*>(hcmd->data[0]);
 
-    auto test = GET_TEST(McastFilterTest, trans);
+    auto test = GET_TEST(McastFilterTestWithoutIface, trans);
     return test->mock_send_cmd_.Call(hcmd->id, mcast_cmd->port_id, mcast_cmd->count,
                                      mcast_cmd->bssid[0], mcast_cmd->addr_list[0 * ETH_ALEN + 0],
                                      mcast_cmd->addr_list[1 * ETH_ALEN + 5],
                                      mcast_cmd->addr_list[2 * ETH_ALEN + 2]);
   }
+
+ protected:
+  struct iwl_mvm* mvm_;
+};
+
+class McastFilterTest : public McastFilterTestWithoutIface {
+ public:
+  McastFilterTest() : helper_(ClientInterfaceHelper(&sim_trans_)) {}
+  ~McastFilterTest() {}
+
+ protected:
+  ClientInterfaceHelper helper_;
 };
 
 TEST_F(McastFilterTest, McastFilterNormal) {
-  ClientInterfaceHelper();
-
   // mock function after the testing environment had been set.
   bindSendCmd(send_cmd_wrapper);
 
@@ -294,7 +340,7 @@
   unbindSendCmd();
 }
 
-TEST_F(McastFilterTest, McastFilterNoActiveInterface) {
+TEST_F(McastFilterTestWithoutIface, McastFilterNoActiveInterface) {
   // mock function after the testing environment had been set.
   bindSendCmd(send_cmd_wrapper);
 
@@ -306,8 +352,7 @@
 }
 
 TEST_F(McastFilterTest, McastFilterAp) {
-  ClientInterfaceHelper();
-  mvmvif_.mac_role = WLAN_INFO_MAC_ROLE_AP;  // overwrite to AP.
+  helper_.mvmvif()->mac_role = WLAN_MAC_ROLE_AP;  // overwrite to AP.
 
   // mock function after the testing environment had been set.
   bindSendCmd(send_cmd_wrapper);
@@ -318,5 +363,42 @@
   unbindSendCmd();
 }
 
+class RegulatoryTest : public Mac80211UcodeTest {
+ public:
+  RegulatoryTest() {}
+  ~RegulatoryTest() {}
+};
+
+TEST_F(RegulatoryTest, RegulatoryTestNormal) {
+  ClientInterfaceHelper helper = ClientInterfaceHelper(&sim_trans_);
+
+  bool changed;
+  wlanphy_country_t country;
+  mtx_lock(&mvm_->mutex);
+  EXPECT_EQ(ZX_OK, iwl_mvm_get_regdomain(mvm_, "TW", MCC_SOURCE_WIFI, &changed, &country));
+  mtx_unlock(&mvm_->mutex);
+
+  EXPECT_EQ(true, changed);
+  EXPECT_EQ(0x54, country.alpha2[0]);
+  EXPECT_EQ(0x57, country.alpha2[1]);
+  EXPECT_EQ(true, mvm_->lar_regdom_set);
+  EXPECT_EQ(MCC_SOURCE_WIFI, mvm_->mcc_src);
+}
+
+TEST_F(RegulatoryTest, TestGetCurrentRegDomain) {
+  ClientInterfaceHelper helper = ClientInterfaceHelper(&sim_trans_);
+
+  bool changed;
+  wlanphy_country_t country = {};
+  mtx_lock(&mvm_->mutex);
+  EXPECT_EQ(ZX_OK, iwl_mvm_get_current_regdomain(mvm_, &changed, &country));
+  mtx_unlock(&mvm_->mutex);
+
+  EXPECT_EQ(true, changed);
+  EXPECT_EQ(0x5a, country.alpha2[0]);  // Becomes 'ZZ' now.
+  EXPECT_EQ(0x5a, country.alpha2[1]);
+  EXPECT_EQ(MCC_SOURCE_GET_CURRENT, mvm_->mcc_src);
+}
+
 }  // namespace
 }  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/mvm-mlme-test.cc b/third_party/iwlwifi/test/mvm-mlme-test.cc
deleted file mode 100644
index 3f6f250..0000000
--- a/third_party/iwlwifi/test/mvm-mlme-test.cc
+++ /dev/null
@@ -1,894 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// To test PHY and MAC device callback functions.
-
-#include <fuchsia/wlan/common/cpp/banjo.h>
-#include <fuchsia/wlan/ieee80211/c/banjo.h>
-#include <fuchsia/wlan/internal/cpp/banjo.h>
-#include <lib/mock-function/mock-function.h>
-#include <zircon/listnode.h>
-#include <zircon/syscalls.h>
-
-#include <list>
-
-#include <zxtest/zxtest.h>
-
-extern "C" {
-#include "third_party/iwlwifi/mvm/mvm.h"
-}
-
-#include "third_party/iwlwifi/platform/ieee80211.h"
-#include "third_party/iwlwifi/platform/mvm-mlme.h"
-#include "third_party/iwlwifi/platform/wlanphy-impl-device.h"
-#include "third_party/iwlwifi/test/mock-trans.h"
-#include "third_party/iwlwifi/test/single-ap-test.h"
-#include "third_party/iwlwifi/test/wlan-pkt-builder.h"
-
-namespace wlan::testing {
-namespace {
-
-static constexpr size_t kListenInterval = 100;
-
-typedef mock_function::MockFunction<void, void*, uint32_t, const void*, size_t,
-                                    const wlan_rx_info_t*>
-    recv_cb_t;
-
-// The wrapper used by wlanmac_ifc_t.recv() to call mock-up.
-void recv_wrapper(void* cookie, uint32_t flags, const uint8_t* data, size_t length,
-                  const wlan_rx_info_t* info) {
-  auto recv = reinterpret_cast<recv_cb_t*>(cookie);
-  recv->Call(cookie, flags, data, length, info);
-}
-
-class WlanDeviceTest : public SingleApTest {
- public:
-  WlanDeviceTest()
-      : mvmvif_sta_{
-            .mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans()),
-            .mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
-            .bss_conf =
-                {
-                    .beacon_int = kListenInterval,
-                },
-        } {
-    device_ = sim_trans_.sim_device();
-  }
-  ~WlanDeviceTest() {}
-
- protected:
-  static constexpr zx_handle_t mlme_channel_ =
-      73939133;  // An arbitrary value not ZX_HANDLE_INVALID
-  static constexpr uint8_t kInvalidBandIdFillByte = 0xa5;
-  static constexpr wlan_info_band_t kInvalidBandId = 0xa5a5a5a5;
-  struct iwl_mvm_vif mvmvif_sta_;  // The mvm_vif settings for station role.
-  wlan::iwlwifi::WlanphyImplDevice* device_;
-};
-
-//////////////////////////////////// Helper Functions  /////////////////////////////////////////////
-TEST_F(WlanDeviceTest, ComposeBandList) {
-  struct iwl_nvm_data nvm_data;
-  wlan_info_band_t bands[WLAN_INFO_BAND_COUNT];
-
-  // nothing enabled
-  memset(&nvm_data, 0, sizeof(nvm_data));
-  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
-  EXPECT_EQ(0, compose_band_list(&nvm_data, bands));
-  EXPECT_EQ(kInvalidBandId, bands[0]);
-  EXPECT_EQ(kInvalidBandId, bands[1]);
-
-  // 2.4GHz only
-  memset(&nvm_data, 0, sizeof(nvm_data));
-  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
-  nvm_data.sku_cap_band_24ghz_enable = true;
-  EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
-  EXPECT_EQ(WLAN_INFO_BAND_2GHZ, bands[0]);
-  EXPECT_EQ(kInvalidBandId, bands[1]);
-
-  // 5GHz only
-  memset(&nvm_data, 0, sizeof(nvm_data));
-  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
-  nvm_data.sku_cap_band_52ghz_enable = true;
-  EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
-  EXPECT_EQ(WLAN_INFO_BAND_5GHZ, bands[0]);
-  EXPECT_EQ(kInvalidBandId, bands[1]);
-
-  // both bands enabled
-  memset(&nvm_data, 0, sizeof(nvm_data));
-  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
-  nvm_data.sku_cap_band_24ghz_enable = true;
-  nvm_data.sku_cap_band_52ghz_enable = true;
-  EXPECT_EQ(2, compose_band_list(&nvm_data, bands));
-  EXPECT_EQ(WLAN_INFO_BAND_2GHZ, bands[0]);
-  EXPECT_EQ(WLAN_INFO_BAND_5GHZ, bands[1]);
-}
-
-// Short-cut to access the iwl_cfg80211_rates[] structure and convert it to 802.11 rate.
-//
-// Args:
-//   index: the index of iwl_cfg80211_rates[].
-//
-// Returns:
-//   the 802.11 rate.
-//
-static unsigned expected_rate(size_t index) {
-  return cfg_rates_to_80211(iwl_cfg80211_rates[index]);
-}
-
-TEST_F(WlanDeviceTest, FillBandInfos) {
-  // The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
-
-  wlan_info_band_t bands[WLAN_INFO_BAND_COUNT] = {
-      WLAN_INFO_BAND_2GHZ,
-      WLAN_INFO_BAND_5GHZ,
-  };
-  wlan_info_band_info_t band_infos[WLAN_INFO_BAND_COUNT] = {};
-
-  fill_band_infos(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, ARRAY_SIZE(bands),
-                  band_infos);
-  // 2.4Ghz
-  wlan_info_band_info_t* exp_band_info = &band_infos[0];
-  EXPECT_EQ(WLAN_INFO_BAND_2GHZ, exp_band_info->band);
-  EXPECT_EQ(true, exp_band_info->ht_supported);
-  EXPECT_EQ(expected_rate(0), exp_band_info->rates[0]);    // 1Mbps
-  EXPECT_EQ(expected_rate(11), exp_band_info->rates[11]);  // 54Mbps
-  EXPECT_EQ(2407, exp_band_info->supported_channels.base_freq);
-  EXPECT_EQ(1, exp_band_info->supported_channels.channels[0]);
-  EXPECT_EQ(13, exp_band_info->supported_channels.channels[12]);
-  // 5GHz
-  exp_band_info = &band_infos[1];
-  EXPECT_EQ(WLAN_INFO_BAND_5GHZ, exp_band_info->band);
-  EXPECT_EQ(true, exp_band_info->ht_supported);
-  EXPECT_EQ(expected_rate(4), exp_band_info->rates[0]);   // 6Mbps
-  EXPECT_EQ(expected_rate(11), exp_band_info->rates[7]);  // 54Mbps
-  EXPECT_EQ(5000, exp_band_info->supported_channels.base_freq);
-  EXPECT_EQ(36, exp_band_info->supported_channels.channels[0]);
-  EXPECT_EQ(165, exp_band_info->supported_channels.channels[24]);
-}
-
-TEST_F(WlanDeviceTest, FillBandInfosOnly5GHz) {
-  // The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
-
-  wlan_info_band_t bands[WLAN_INFO_BAND_COUNT] = {
-      WLAN_INFO_BAND_5GHZ,
-      0,
-  };
-  wlan_info_band_info_t band_infos[WLAN_INFO_BAND_COUNT] = {};
-
-  fill_band_infos(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, 1, band_infos);
-  // 5GHz
-  wlan_info_band_info_t* exp_band_info = &band_infos[0];
-  EXPECT_EQ(WLAN_INFO_BAND_5GHZ, exp_band_info->band);
-  EXPECT_EQ(true, exp_band_info->ht_supported);
-  EXPECT_EQ(expected_rate(4), exp_band_info->rates[0]);   // 6Mbps
-  EXPECT_EQ(expected_rate(11), exp_band_info->rates[7]);  // 54Mbps
-  EXPECT_EQ(5000, exp_band_info->supported_channels.base_freq);
-  EXPECT_EQ(36, exp_band_info->supported_channels.channels[0]);
-  EXPECT_EQ(165, exp_band_info->supported_channels.channels[24]);
-  // index 1 should be empty.
-  exp_band_info = &band_infos[1];
-  EXPECT_EQ(false, exp_band_info->ht_supported);
-  EXPECT_EQ(0x00, exp_band_info->rates[0]);
-  EXPECT_EQ(0x00, exp_band_info->rates[7]);
-  EXPECT_EQ(0, exp_band_info->supported_channels.channels[0]);
-}
-
-/////////////////////////////////////       MAC       //////////////////////////////////////////////
-
-TEST_F(WlanDeviceTest, MacQuery) {
-  // Test input null pointers
-  uint32_t options = 0;
-  void* whatever = &options;
-  ASSERT_EQ(ZX_ERR_INVALID_ARGS, wlanmac_ops.query(nullptr, options, nullptr));
-  ASSERT_EQ(ZX_ERR_INVALID_ARGS, wlanmac_ops.query(whatever, options, nullptr));
-  ASSERT_EQ(ZX_ERR_INVALID_ARGS,
-            wlanmac_ops.query(nullptr, options, reinterpret_cast<wlanmac_info*>(whatever)));
-
-  wlanmac_info_t info = {};
-  ASSERT_EQ(ZX_OK, wlanmac_ops.query(&mvmvif_sta_, options, &info));
-  EXPECT_EQ(WLAN_INFO_MAC_ROLE_CLIENT, info.mac_role);
-
-  //
-  // The below code assumes the test/sim-default-nvm.cc contains 2 bands.
-  //
-  //   .bands[0]: WLAN_INFO_BAND_2GHZ
-  //   .bands[1]: WLAN_INFO_BAND_5GHZ
-  //
-  ASSERT_EQ(2, info.bands_count);
-  EXPECT_EQ(expected_rate(0), info.bands[0].rates[0]);    // 1 Mbps
-  EXPECT_EQ(expected_rate(7), info.bands[0].rates[7]);    // 18 Mbps
-  EXPECT_EQ(expected_rate(11), info.bands[0].rates[11]);  // 54 Mbps
-  EXPECT_EQ(expected_rate(4), info.bands[1].rates[0]);    // 6 Mbps
-  EXPECT_EQ(165, info.bands[1].supported_channels.channels[24]);
-}
-
-TEST_F(WlanDeviceTest, MacStart) {
-  // Test input null pointers
-  wlanmac_ifc_protocol_ops_t proto_ops = {
-      .recv = recv_wrapper,
-  };
-  wlanmac_ifc_protocol_t ifc = {.ops = &proto_ops};
-  zx_handle_t mlme_channel;
-  ASSERT_EQ(wlanmac_ops.start(nullptr, &ifc, &mlme_channel), ZX_ERR_INVALID_ARGS);
-  ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, nullptr, &mlme_channel), ZX_ERR_INVALID_ARGS);
-  ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, nullptr), ZX_ERR_INVALID_ARGS);
-
-  // Test callback function
-  recv_cb_t mock_recv;  // To mock up the wlanmac_ifc_t.recv().
-  mvmvif_sta_.mlme_channel = mlme_channel_;
-  ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, &mlme_channel), ZX_OK);
-  // Expect the above line would copy the 'ifc'. Then set expectation below and fire test.
-  mock_recv.ExpectCall(&mock_recv, 0, nullptr, 0, nullptr);
-  mvmvif_sta_.ifc.ops->recv(&mock_recv, 0, nullptr, 0, nullptr);
-  mock_recv.VerifyAndClear();
-}
-
-TEST_F(WlanDeviceTest, MacStartSmeChannel) {
-  // The normal case. A channel will be transferred to MLME.
-  constexpr zx_handle_t from_devmgr = mlme_channel_;
-  mvmvif_sta_.mlme_channel = from_devmgr;
-  wlanmac_ifc_protocol_ops_t proto_ops = {
-      .recv = recv_wrapper,
-  };
-  wlanmac_ifc_protocol_t ifc = {.ops = &proto_ops};
-  zx_handle_t mlme_channel;
-  ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, &mlme_channel), ZX_OK);
-  ASSERT_EQ(mlme_channel, from_devmgr);                    // The channel handle is returned.
-  ASSERT_EQ(mvmvif_sta_.mlme_channel, ZX_HANDLE_INVALID);  // Driver no longer holds the ownership.
-
-  // Since the driver no longer owns the handle, the start should fail.
-  ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, &mlme_channel), ZX_ERR_ALREADY_BOUND);
-}
-
-TEST_F(WlanDeviceTest, MacRelease) {
-  // Allocate an instance so that we can free that in mac_release().
-  struct iwl_mvm_vif* mvmvif =
-      reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
-
-  // Create a channel. Let this test case holds one end while driver holds the other end.
-  char dummy[1];
-  zx_handle_t case_end;
-  ASSERT_EQ(zx_channel_create(0 /* option */, &case_end, &mvmvif->mlme_channel), ZX_OK);
-  ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0), ZX_OK);
-
-  // Call release and the sme channel should be closed so that we will get a peer-close error while
-  // trying to write any data to it.
-  device_mac_ops.release(mvmvif);
-  ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0),
-            ZX_ERR_PEER_CLOSED);
-}
-
-/////////////////////////////////////       PHY       //////////////////////////////////////////////
-
-TEST_F(WlanDeviceTest, PhyQuery) {
-  wlanphy_impl_info_t info = {};
-
-  // Test input null pointers
-  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanphyImplQuery(nullptr));
-  ASSERT_EQ(ZX_OK, device_->WlanphyImplQuery(&info));
-
-  // Normal case
-  ASSERT_EQ(ZX_OK, device_->WlanphyImplQuery(&info));
-  EXPECT_EQ(WLAN_INFO_MAC_ROLE_CLIENT, info.supported_mac_roles);
-}
-
-TEST_F(WlanDeviceTest, PhyPartialCreateCleanup) {
-  wlanphy_impl_create_iface_req_t req = {
-      .role = WLAN_INFO_MAC_ROLE_CLIENT,
-      .mlme_channel = mlme_channel_,
-  };
-  uint16_t iface_id;
-  struct iwl_trans* iwl_trans = sim_trans_.iwl_trans();
-
-  // Test input null pointers
-  ASSERT_OK(phy_create_iface(iwl_trans, &req, &iface_id));
-
-  // Ensure mvmvif got created and indexed.
-  struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
-  ASSERT_NOT_NULL(mvm->mvmvif[iface_id]);
-
-  // Ensure partial create failure removes it from the index.
-  phy_create_iface_undo(iwl_trans, iface_id);
-  ASSERT_NULL(mvm->mvmvif[iface_id]);
-}
-
-TEST_F(WlanDeviceTest, PhyCreateDestroySingleInterface) {
-  wlanphy_impl_create_iface_req_t req = {
-      .role = WLAN_INFO_MAC_ROLE_CLIENT,
-      .mlme_channel = mlme_channel_,
-  };
-  uint16_t iface_id;
-
-  // Test input null pointers
-  ASSERT_EQ(device_->WlanphyImplCreateIface(nullptr, &iface_id), ZX_ERR_INVALID_ARGS);
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, nullptr), ZX_ERR_INVALID_ARGS);
-  ASSERT_EQ(device_->WlanphyImplCreateIface(nullptr, nullptr), ZX_ERR_INVALID_ARGS);
-
-  // Test invalid inputs
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(MAX_NUM_MVMVIF), ZX_ERR_INVALID_ARGS);
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_ERR_NOT_FOUND);  // hasn't been added yet.
-
-  // To verify the internal state of MVM driver.
-  struct iwl_mvm* mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
-
-  // Add interface
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
-  ASSERT_EQ(iface_id, 0);  // the first interface should have id 0.
-  struct iwl_mvm_vif* mvmvif = mvm->mvmvif[iface_id];
-  ASSERT_NE(mvmvif, nullptr);
-  ASSERT_EQ(mvmvif->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
-  // Count includes phy device in addition to the newly created mac device.
-  ASSERT_EQ(fake_parent_->descendant_count(), 2);
-  device_->zxdev()->GetLatestChild()->InitOp();
-
-  // Remove interface
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_OK);
-  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
-  ASSERT_EQ(mvm->mvmvif[iface_id], nullptr);
-  ASSERT_EQ(fake_parent_->descendant_count(), 1);
-}
-
-TEST_F(WlanDeviceTest, PhyCreateDestroyMultipleInterfaces) {
-  wlanphy_impl_create_iface_req_t req = {
-      .role = WLAN_INFO_MAC_ROLE_CLIENT,
-      .mlme_channel = mlme_channel_,
-  };
-  uint16_t iface_id;
-  struct iwl_trans* iwl_trans = sim_trans_.iwl_trans();
-  struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);  // To verify the internal state of MVM driver
-
-  // Add 1st interface
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
-  ASSERT_EQ(iface_id, 0);
-  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
-  ASSERT_EQ(fake_parent_->descendant_count(), 2);
-  device_->zxdev()->GetLatestChild()->InitOp();
-
-  // Add 2nd interface
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
-  ASSERT_EQ(iface_id, 1);
-  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
-  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
-  ASSERT_EQ(fake_parent_->descendant_count(), 3);
-  device_->zxdev()->GetLatestChild()->InitOp();
-
-  // Add 3rd interface
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
-  ASSERT_EQ(iface_id, 2);
-  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
-  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
-  ASSERT_EQ(fake_parent_->descendant_count(), 4);
-  device_->zxdev()->GetLatestChild()->InitOp();
-
-  // Remove the 2nd interface
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(1), ZX_OK);
-  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
-  ASSERT_EQ(mvm->mvmvif[1], nullptr);
-  ASSERT_EQ(fake_parent_->descendant_count(), 3);
-
-  // Add a new interface and it should be the 2nd one.
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
-  ASSERT_EQ(iface_id, 1);
-  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
-  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
-  ASSERT_EQ(fake_parent_->descendant_count(), 4);
-  device_->zxdev()->GetLatestChild()->InitOp();
-
-  // Add 4th interface
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
-  ASSERT_EQ(iface_id, 3);
-  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
-  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
-  ASSERT_EQ(fake_parent_->descendant_count(), 5);
-  device_->zxdev()->GetLatestChild()->InitOp();
-
-  // Add 5th interface and it should fail
-  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_ERR_NO_RESOURCES);
-  ASSERT_EQ(fake_parent_->descendant_count(), 5);
-
-  // Remove the 2nd interface
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(1), ZX_OK);
-  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
-  ASSERT_EQ(mvm->mvmvif[1], nullptr);
-  ASSERT_EQ(fake_parent_->descendant_count(), 4);
-
-  // Remove the 3rd interface
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(2), ZX_OK);
-  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
-  ASSERT_EQ(mvm->mvmvif[2], nullptr);
-  ASSERT_EQ(fake_parent_->descendant_count(), 3);
-
-  // Remove the 4th interface
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(3), ZX_OK);
-  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
-  ASSERT_EQ(mvm->mvmvif[3], nullptr);
-  ASSERT_EQ(fake_parent_->descendant_count(), 2);
-
-  // Remove the 1st interface
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_OK);
-  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
-  ASSERT_EQ(mvm->mvmvif[0], nullptr);
-  ASSERT_EQ(fake_parent_->descendant_count(), 1);
-
-  // Remove the 1st interface again and it should fail.
-  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_ERR_NOT_FOUND);
-  ASSERT_EQ(fake_parent_->descendant_count(), 1);
-}
-
-// The class for WLAN device MAC testing.
-//
-class MacInterfaceTest : public WlanDeviceTest, public MockTrans {
- public:
-  MacInterfaceTest() : ifc_{ .ops = &proto_ops_, } , proto_ops_{ .recv = recv_wrapper, } {
-    mvmvif_sta_.mlme_channel = mlme_channel_;
-    zx_handle_t mlme_channel;
-    ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc_, &mlme_channel), ZX_OK);
-
-    // Add the interface to MVM instance.
-    mvmvif_sta_.mvm->mvmvif[0] = &mvmvif_sta_;
-  }
-
-  ~MacInterfaceTest() {
-    VerifyExpectation();  // Ensure all expectations had been met.
-
-    // Restore the original callback for other test cases not using the mock.
-    if (original_send_cmd) {
-      sim_trans_.iwl_trans()->ops->send_cmd = original_send_cmd;
-    }
-
-    // Stop the MAC to free resources we allocated.
-    // This must be called after we verify the expected commands and restore the mock command
-    // callback so that the stop command doesn't mess up the test case expectation.
-    wlanmac_ops.stop(&mvmvif_sta_);
-    VerifyStaHasBeenRemoved();
-  }
-
-  // Used in MockCommand constructor to indicate if the command needs to be either
-  //
-  //   - returned immediately (with a status code), or
-  //   - passed to the sim_mvm.c.
-  //
-  enum SimMvmBehavior {
-    kSimMvmReturnWithStatus,
-    kSimMvmBypassToSimMvm,
-  };
-
-  // A flexible mock-up of firmware command for testing code. Testing code can decide to either call
-  // the simulated firmware or return the status code immediately.
-  //
-  //   cmd_id: the command ID. Sometimes composed with WIDE_ID() macro.
-  //   behavior: determine what this mockup command is to do.
-  //   status: the status code to return when behavior is 'kSimMvmReturnWithStatus'.
-  //
-  class MockCommand {
-   public:
-    MockCommand(uint32_t cmd_id, SimMvmBehavior behavior, zx_status_t status)
-        : cmd_id_(cmd_id), behavior_(behavior), status_(status) {}
-    MockCommand(uint32_t cmd_id) : MockCommand(cmd_id, kSimMvmBypassToSimMvm, ZX_OK) {}
-
-    ~MockCommand() {}
-
-    uint32_t cmd_id_;
-    SimMvmBehavior behavior_;
-    zx_status_t status_;
-  };
-  typedef std::list<MockCommand> expected_cmd_id_list;
-  typedef zx_status_t (*fp_send_cmd)(struct iwl_trans* trans, struct iwl_host_cmd* cmd);
-
-  // Public for MockSendCmd().
-  expected_cmd_id_list expected_cmd_ids;
-  fp_send_cmd original_send_cmd;
-
- protected:
-  zx_status_t SetChannel(const wlan_channel_t* channel) {
-    uint32_t option = 0;
-    return wlanmac_ops.set_channel(&mvmvif_sta_, option, channel);
-  }
-
-  zx_status_t ConfigureBss(const bss_config_t* config) {
-    uint32_t option = 0;
-    return wlanmac_ops.configure_bss(&mvmvif_sta_, option, config);
-  }
-
-  zx_status_t ConfigureAssoc(const wlan_assoc_ctx_t* config) {
-    uint32_t option = 0;
-    return wlanmac_ops.configure_assoc(&mvmvif_sta_, option, config);
-  }
-
-  zx_status_t ClearAssoc() {
-    uint32_t option = 0;
-    uint8_t peer_addr[fuchsia_wlan_ieee80211_MAC_ADDR_LEN];  // Not used since all info were
-                                                             // saved in mvmvif_sta_ already.
-    return wlanmac_ops.clear_assoc(&mvmvif_sta_, option, peer_addr);
-  }
-
-  zx_status_t SetKey(const wlan_key_config_t* key_config) {
-    uint32_t option = 0;
-    IWL_INFO(nullptr, "Calling set_key");
-    return wlanmac_ops.set_key(&mvmvif_sta_, option, key_config);
-  }
-  // The following functions are for mocking up the firmware commands.
-  //
-  // The mock function will return the special error ZX_ERR_INTERNAL when the expectation
-  // is not expected.
-
-  // Set the expected commands sending to the firmware.
-  //
-  // Args:
-  //   cmd_ids: list of expected commands. Will be matched in order.
-  //
-  void ExpectSendCmd(const expected_cmd_id_list& cmd_ids) {
-    expected_cmd_ids = cmd_ids;
-
-    // Re-define the 'dev' field in the 'struct iwl_trans' to a test instance of this class.
-    sim_trans_.iwl_trans()->dev = reinterpret_cast<struct device*>(this);
-
-    // Setup the mock function for send command.
-    original_send_cmd = sim_trans_.iwl_trans()->ops->send_cmd;
-    sim_trans_.iwl_trans()->ops->send_cmd = MockSendCmd;
-  }
-
-  static zx_status_t MockSendCmd(struct iwl_trans* trans, struct iwl_host_cmd* cmd) {
-    MacInterfaceTest* this_ = reinterpret_cast<MacInterfaceTest*>(trans->dev);
-
-    // remove the first one and match.
-    expected_cmd_id_list& expected = this_->expected_cmd_ids;
-    ZX_ASSERT_MSG(!expected.empty(),
-                  "A command (0x%04x) is going to send, but no command is expected.\n", cmd->id);
-
-    // check the command ID.
-    auto exp = expected.front();
-    ZX_ASSERT_MSG(exp.cmd_id_ == cmd->id,
-                  "The command doesn't match! Expect: 0x%04x, actual: 0x%04x.\n", exp.cmd_id_,
-                  cmd->id);
-    expected.pop_front();
-
-    if (exp.behavior_ == kSimMvmBypassToSimMvm) {
-      return this_->original_send_cmd(trans, cmd);
-    } else {
-      return exp.status_;
-    }
-  }
-
-  void VerifyExpectation() {
-    for (expected_cmd_id_list::iterator it = expected_cmd_ids.begin(); it != expected_cmd_ids.end();
-         it++) {
-      printf("  ==> 0x%04x\n", it->cmd_id_);
-    }
-    ASSERT_TRUE(expected_cmd_ids.empty(), "The expected command set is not empty.");
-
-    mock_tx_.VerifyAndClear();
-  }
-
-  void VerifyStaHasBeenRemoved() {
-    auto mvm = mvmvif_sta_.mvm;
-
-    for (size_t i = 0; i < ARRAY_SIZE(mvm->fw_id_to_mac_id); i++) {
-      struct iwl_mvm_sta* mvm_sta = mvm->fw_id_to_mac_id[i];
-      ASSERT_EQ(nullptr, mvm_sta);
-    }
-    ASSERT_EQ(0, mvm->vif_count);
-  }
-
-  // Mock function for Tx.
-  mock_function::MockFunction<zx_status_t,  // return value
-                              size_t,       // packet size
-                              uint16_t,     // cmd + group_id
-                              int           // txq_id
-                              >
-      mock_tx_;
-
-  static zx_status_t tx_wrapper(struct iwl_trans* trans, struct ieee80211_mac_packet* pkt,
-                                const struct iwl_device_cmd* dev_cmd, int txq_id) {
-    auto test = GET_TEST(MacInterfaceTest, trans);
-    return test->mock_tx_.Call(pkt->header_size + pkt->headroom_used_size + pkt->body_size,
-                               WIDE_ID(dev_cmd->hdr.group_id, dev_cmd->hdr.cmd), txq_id);
-  }
-
-  wlanmac_ifc_protocol_t ifc_;
-  wlanmac_ifc_protocol_ops_t proto_ops_;
-  static constexpr bss_config_t kBssConfig = {
-      .bssid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
-      .bss_type = BSS_TYPE_INFRASTRUCTURE,
-      .remote = true,
-  };
-  static constexpr wlan_assoc_ctx_t kAssocCtx = {
-      .listen_interval = kListenInterval,
-  };
-};
-
-// Test the set_channel().
-//
-TEST_F(MacInterfaceTest, TestSetChannel) {
-  ExpectSendCmd(expected_cmd_id_list({
-      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for add_chanctx
-      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for change_chanctx
-      MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
-      MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
-  }));
-
-  mvmvif_sta_.csa_bcn_pending = true;  // Expect to be clear because this is client role.
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  EXPECT_EQ(false, mvmvif_sta_.csa_bcn_pending);
-}
-
-// Test the unsupported MAC role.
-//
-TEST_F(MacInterfaceTest, TestSetChannelWithUnsupportedRole) {
-  ExpectSendCmd(expected_cmd_id_list({
-      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for add_chanctx
-      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for change_chanctx
-  }));
-
-  mvmvif_sta_.mac_role = WLAN_INFO_MAC_ROLE_AP;
-  ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, SetChannel(&kChannel));
-}
-
-// Tests calling SetChannel()/ConfigureBss() again without ConfigureAssoc()/ClearAssoc()
-TEST_F(MacInterfaceTest, DuplicateSetChannel) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  struct iwl_mvm_sta* mvm_sta = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  struct iwl_mvm_phy_ctxt* phy_ctxt = mvmvif_sta_.phy_ctxt;
-  ASSERT_NE(nullptr, phy_ctxt);
-  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
-  // Call SetChannel() again. This should return the same phy context but ConfigureBss()
-  // should setup a new STA.
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  struct iwl_mvm_phy_ctxt* new_phy_ctxt = mvmvif_sta_.phy_ctxt;
-  ASSERT_NE(nullptr, new_phy_ctxt);
-  ASSERT_EQ(phy_ctxt, new_phy_ctxt);
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  struct iwl_mvm_sta* new_mvm_sta = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  // Now Associate and disassociate - this should release and reset the phy ctxt.
-  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
-  ASSERT_EQ(IWL_STA_AUTHORIZED, new_mvm_sta->sta_state);
-  ASSERT_EQ(true, mvmvif_sta_.bss_conf.assoc);
-  ASSERT_EQ(kListenInterval, mvmvif_sta_.bss_conf.listen_interval);
-
-  ASSERT_EQ(ZX_OK, ClearAssoc());
-  ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
-  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.ap_sta_id);
-}
-
-// Test ConfigureBss()
-//
-TEST_F(MacInterfaceTest, TestConfigureBss) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-
-  ExpectSendCmd(expected_cmd_id_list({
-      MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
-      MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
-      MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
-      MockCommand(WIDE_ID(LONG_GROUP, SCD_QUEUE_CFG)),
-      MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
-  }));
-
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  // Ensure the BSSID was copied into mvmvif
-  ASSERT_EQ(memcmp(mvmvif_sta_.bss_conf.bssid, kBssConfig.bssid, ETH_ALEN), 0);
-  ASSERT_EQ(memcmp(mvmvif_sta_.bssid, kBssConfig.bssid, ETH_ALEN), 0);
-}
-
-// Test duplicate BSS config.
-//
-TEST_F(MacInterfaceTest, DuplicateConfigureBss) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  ASSERT_EQ(ZX_ERR_ALREADY_EXISTS, ConfigureBss(&kBssConfig));
-}
-
-// Test unsupported bss_type.
-//
-TEST_F(MacInterfaceTest, UnsupportedBssType) {
-  static constexpr bss_config_t kUnsupportedBssConfig = {
-      .bssid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
-      .bss_type = BSS_TYPE_INDEPENDENT,
-      .remote = true,
-  };
-  ASSERT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kUnsupportedBssConfig));
-}
-
-// Test failed ADD_STA command.
-//
-TEST_F(MacInterfaceTest, TestFailedAddSta) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-
-  ExpectSendCmd(expected_cmd_id_list({
-      MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD), kSimMvmReturnWithStatus,
-                  ZX_ERR_BUFFER_TOO_SMALL /* an arbitrary error */),
-  }));
-
-  ASSERT_EQ(ZX_ERR_BUFFER_TOO_SMALL, ConfigureBss(&kBssConfig));
-}
-
-// Test exception handling in driver.
-//
-TEST_F(MacInterfaceTest, TestExceptionHandling) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-
-  // Test the beacon interval checking.
-  mvmvif_sta_.bss_conf.beacon_int = 0;
-  EXPECT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kBssConfig));
-  mvmvif_sta_.bss_conf.beacon_int = 16;  // which just passes the check.
-
-  // Test the phy_ctxt checking.
-  auto backup_phy_ctxt = mvmvif_sta_.phy_ctxt;
-  mvmvif_sta_.phy_ctxt = nullptr;
-  EXPECT_EQ(ZX_ERR_BAD_STATE, ConfigureBss(&kBssConfig));
-  mvmvif_sta_.phy_ctxt = backup_phy_ctxt;
-
-  // Test the case we run out of slots for STA.
-  //
-  // In the constructor of the test, mvmvif_sta_ had been added once. So we would expect the
-  // following (IWL_MVM_STATION_COUNT - 1) adding would be successful as well.
-  //
-  for (size_t i = 0; i < IWL_MVM_STATION_COUNT - 1; i++) {
-    // Pretent the STA is not assigned so that we can add it again.
-    mvmvif_sta_.ap_sta_id = IWL_MVM_INVALID_STA;
-    ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  }
-  // However, the last one should fail because we run out of all slots in fw_id_to_mac_id[].
-  mvmvif_sta_.ap_sta_id = IWL_MVM_INVALID_STA;
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-}
-
-// The test is used to test the typical procedure to connect to an open network.
-//
-TEST_F(MacInterfaceTest, AssociateToOpenNetwork) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  struct iwl_mvm_sta* mvm_sta = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
-  struct iwl_mvm* mvm = mvmvif_sta_.mvm;
-  ASSERT_GT(list_length(&mvm->time_event_list), 0);
-
-  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
-  ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
-  ASSERT_EQ(true, mvmvif_sta_.bss_conf.assoc);
-  ASSERT_EQ(kListenInterval, mvmvif_sta_.bss_conf.listen_interval);
-
-  ASSERT_EQ(ZX_OK, ClearAssoc());
-  ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
-  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.ap_sta_id);
-  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
-}
-
-// Back to back calls of ClearAssoc().
-TEST_F(MacInterfaceTest, ClearAssocAfterClearAssoc) {
-  ASSERT_NE(ZX_OK, ClearAssoc());
-  ASSERT_NE(ZX_OK, ClearAssoc());
-}
-
-// ClearAssoc() should cleanup when called without Assoc
-TEST_F(MacInterfaceTest, ClearAssocAfterNoAssoc) {
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  struct iwl_mvm_sta* mvm_sta = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
-  struct iwl_mvm* mvm = mvmvif_sta_.mvm;
-  ASSERT_GT(list_length(&mvm->time_event_list), 0);
-
-  ASSERT_EQ(ZX_OK, ClearAssoc());
-  ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
-  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.ap_sta_id);
-  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
-  // Call ClearAssoc() again to check if it is handled correctly.
-  ASSERT_NE(ZX_OK, ClearAssoc());
-}
-
-TEST_F(MacInterfaceTest, AssociateToOpenNetworkNullStation) {
-  SetChannel(&kChannel);
-  ConfigureBss(&kBssConfig);
-
-  // Replace the STA pointer with NULL and expect the association will fail.
-  auto org = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = nullptr;
-
-  ASSERT_EQ(ZX_ERR_BAD_STATE, ConfigureAssoc(&kAssocCtx));
-
-  // Expect error while disassociating a non-existing association.
-  ASSERT_EQ(ZX_ERR_BAD_STATE, ClearAssoc());
-
-  // We have to recover the pointer so that the MAC stop function can recycle the memory.
-  mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = org;
-}
-
-TEST_F(MacInterfaceTest, ClearAssocAfterFailedAssoc) {
-  SetChannel(&kChannel);
-  ConfigureBss(&kBssConfig);
-
-  struct iwl_mvm* mvm = mvmvif_sta_.mvm;
-  ASSERT_GT(list_length(&mvm->time_event_list), 0);
-  // Replace the STA pointer with NULL and expect the association will fail.
-  auto org = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = nullptr;
-
-  ASSERT_EQ(ZX_ERR_BAD_STATE, ConfigureAssoc(&kAssocCtx));
-  // Now put back the original STA pointer so ClearAssoc runs and also
-  // to recycle allocated memory
-  mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = org;
-  ASSERT_GT(list_length(&mvm->time_event_list), 0);
-
-  // Expect error while disassociating a non-existing association.
-  ASSERT_EQ(ZX_OK, ClearAssoc());
-  ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
-  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.ap_sta_id);
-  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
-  // Call ClearAssoc() again to check if it is handled correctly.
-  ASSERT_NE(ZX_OK, ClearAssoc());
-}
-
-// Check to ensure keys are set during assoc and deleted after disassoc
-// for now use open network
-TEST_F(MacInterfaceTest, SetKeysTest) {
-  constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
-  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
-  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
-  struct iwl_mvm_sta* mvm_sta = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
-  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
-  struct iwl_mvm* mvm = mvmvif_sta_.mvm;
-  ASSERT_GT(list_length(&mvm->time_event_list), 0);
-
-  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
-  ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
-  ASSERT_EQ(true, mvmvif_sta_.bss_conf.assoc);
-  ASSERT_EQ(kListenInterval, mvmvif_sta_.bss_conf.listen_interval);
-
-  char keybuf[sizeof(wlan_key_config_t) + 16];
-  wlan_key_config_t* key_config = (wlan_key_config_t*)keybuf;
-  // PAIRWISE KEY
-  key_config->cipher_type = 4;
-  key_config->key_type = 1;
-  key_config->key_idx = 0;
-  key_config->key_len = 16;
-  memcpy(key_config->cipher_oui, kIeeeOui, 3);
-  ASSERT_EQ(ZX_OK, SetKey((const wlan_key_config_t*)key_config));
-  // Expect bit 0 to be set.
-  ASSERT_EQ(*mvm->fw_key_table, 0x1);
-  // GROUP KEY
-  key_config->key_type = 2;
-  key_config->key_idx = 1;
-  ASSERT_EQ(ZX_OK, SetKey((const wlan_key_config_t*)key_config));
-  // Expect bit 1 to be set as well.
-  ASSERT_EQ(*mvm->fw_key_table, 0x3);
-  ASSERT_EQ(ZX_OK, ClearAssoc());
-  ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
-  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.ap_sta_id);
-  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
-  // Both the keys should have been deleted.
-  ASSERT_EQ(*mvm->fw_key_table, 0x0);
-}
-
-TEST_F(MacInterfaceTest, TxPktNotSupportedRole) {
-  SetChannel(&kChannel);
-  ConfigureBss(&kBssConfig);
-  BIND_TEST(sim_trans_.iwl_trans());
-
-  // Set to an unsupported role.
-  mvmvif_sta_.mac_role = WLAN_INFO_MAC_ROLE_AP;
-
-  bindTx(tx_wrapper);
-  WlanPktBuilder builder;
-  std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
-  ASSERT_EQ(ZX_ERR_INVALID_ARGS, wlanmac_ops.queue_tx(&mvmvif_sta_, 0, wlan_pkt->wlan_pkt()));
-  unbindTx();
-}
-
-// To test if a packet can be sent out.
-TEST_F(MacInterfaceTest, TxPkt) {
-  SetChannel(&kChannel);
-  ConfigureBss(&kBssConfig);
-  BIND_TEST(sim_trans_.iwl_trans());
-
-  bindTx(tx_wrapper);
-  WlanPktBuilder builder;
-  std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
-  mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len(), WIDE_ID(0, TX_CMD), IWL_MVM_DQA_MIN_MGMT_QUEUE);
-  ASSERT_EQ(ZX_OK, wlanmac_ops.queue_tx(&mvmvif_sta_, 0, wlan_pkt->wlan_pkt()));
-  unbindTx();
-}
-
-}  // namespace
-}  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/mvm-test.cc b/third_party/iwlwifi/test/mvm-test.cc
index 3c455e3..b83fd8b 100644
--- a/third_party/iwlwifi/test/mvm-test.cc
+++ b/third_party/iwlwifi/test/mvm-test.cc
@@ -3,8 +3,9 @@
 // found in the LICENSE file.
 
 #include <lib/mock-function/mock-function.h>
-#include <lib/zircon-internal/thread_annotations.h>
+#include <zircon/compiler.h>
 
+#include <iterator>
 #include <memory>
 
 #include <zxtest/zxtest.h>
@@ -14,10 +15,11 @@
 #include "third_party/iwlwifi/mvm/time-event.h"
 }
 
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 #include "third_party/iwlwifi/platform/memory.h"
-#include "third_party/iwlwifi/test/fake-ucode-capa-test.h"
+#include "third_party/iwlwifi/test/fake-ucode-test.h"
 #include "third_party/iwlwifi/test/mock-trans.h"
+#include "third_party/iwlwifi/test/sim-time-event.h"
 #include "third_party/iwlwifi/test/single-ap-test.h"
 #include "third_party/iwlwifi/test/wlan-pkt-builder.h"
 
@@ -27,7 +29,7 @@
 
 // Helper function to create a PHY context for the interface.
 //
-static void setup_phy_ctxt(struct iwl_mvm_vif* mvmvif) TA_NO_THREAD_SAFETY_ANALYSIS {
+static void setup_phy_ctxt(struct iwl_mvm_vif* mvmvif) __TA_NO_THREAD_SAFETY_ANALYSIS {
   // Create a PHY context and assign it to mvmvif.
   wlan_channel_t chandef = {
       // any arbitrary values
@@ -70,38 +72,38 @@
 
 class MvmTest : public SingleApTest {
  public:
-  MvmTest() TA_NO_THREAD_SAFETY_ANALYSIS {
+  MvmTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
     mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans());
     mvmvif_ = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
     mvmvif_->mvm = mvm_;
-    mvmvif_->mac_role = WLAN_INFO_MAC_ROLE_CLIENT;
-    mvmvif_->ifc.ops = reinterpret_cast<wlanmac_ifc_protocol_ops_t*>(
-        calloc(1, sizeof(wlanmac_ifc_protocol_ops_t)));
+    mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
+    mvmvif_->ifc.ops = reinterpret_cast<wlan_softmac_ifc_protocol_ops_t*>(
+        calloc(1, sizeof(wlan_softmac_ifc_protocol_ops_t)));
     mvm_->mvmvif[0] = mvmvif_;
     mvm_->vif_count++;
 
     mtx_lock(&mvm_->mutex);
   }
 
-  ~MvmTest() TA_NO_THREAD_SAFETY_ANALYSIS {
+  ~MvmTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
     free(mvmvif_->ifc.ops);
     free(mvmvif_);
     mtx_unlock(&mvm_->mutex);
   }
 
  protected:
-  // This function is kind of dirty. It hijacks the wlanmac_ifc_protocol_t.recv() so that we can
-  // save the rx_info passed to MLME.  See TearDown() for cleanup logic related to this function.
+  // This function is kind of dirty. It hijacks the wlan_softmac_ifc_protocol_t.recv() so that we
+  // can save the rx_info passed to MLME.  See TearDown() for cleanup logic related to this
+  // function.
   void MockRecv(TestCtx* ctx) {
     // TODO(fxbug.dev/43218): replace rxq->napi with interface instance so that we can map to
     // mvmvif.
-    mvmvif_->ifc.ctx = ctx;  // 'ctx' was used as 'wlanmac_ifc_protocol_t*', but we override it
+    mvmvif_->ifc.ctx = ctx;  // 'ctx' was used as 'wlan_softmac_ifc_protocol_t*', but we override it
                              // with 'TestCtx*'.
-    mvmvif_->ifc.ops->recv = [](void* ctx, uint32_t flags, const uint8_t* data_buffer,
-                                size_t data_size, const wlan_rx_info_t* info) {
+    mvmvif_->ifc.ops->recv = [](void* ctx, const wlan_rx_packet_t* packet) {
       TestCtx* test_ctx = reinterpret_cast<TestCtx*>(ctx);
-      test_ctx->rx_info = *info;
-      test_ctx->frame_len = data_size;
+      test_ctx->rx_info = packet->info;
+      test_ctx->frame_len = packet->mac_frame_size;
     };
   }
 
@@ -350,33 +352,36 @@
 ///////////////////////////////////////////////////////////////////////////////
 //                                  Scan Test
 //
-class ScanTest : public MvmTest {
+class PassiveScanTest : public MvmTest {
  public:
-  ScanTest() {
+  PassiveScanTest() {
     // Fake callback registered to capture scan completion responses.
-    ops.hw_scan_complete = [](void* ctx, const wlan_hw_scan_result_t* result) {
+    ops.scan_complete = [](void* ctx, zx_status_t status, uint64_t scan_id) {
+      // TODO(fxbug.dev/88934): scan_id is always 0
+      EXPECT_EQ(scan_id, 0);
       struct ScanResult* sr = (struct ScanResult*)ctx;
       sr->sme_notified = true;
-      sr->success = (result->code == WLAN_HW_SCAN_SUCCESS ? true : false);
+      sr->success = (status == ZX_OK ? true : false);
     };
 
     mvmvif_sta.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
-    mvmvif_sta.mac_role = WLAN_INFO_MAC_ROLE_CLIENT;
+    mvmvif_sta.mac_role = WLAN_MAC_ROLE_CLIENT;
     mvmvif_sta.ifc.ops = &ops;
     mvmvif_sta.ifc.ctx = &scan_result;
 
-    // This can be moved out or overridden when we add other scan types.
-    scan_config.scan_type = WLAN_HW_SCAN_TYPE_PASSIVE;
-
     trans_ = sim_trans_.iwl_trans();
   }
 
-  ~ScanTest() {}
+  ~PassiveScanTest() {}
 
   struct iwl_trans* trans_;
-  wlanmac_ifc_protocol_ops_t ops;
+  wlan_softmac_ifc_protocol_ops_t ops;
   struct iwl_mvm_vif mvmvif_sta;
-  wlan_hw_scan_config_t scan_config{.num_channels = 4, .channels = {7, 1, 40, 136}};
+  uint8_t channels_to_scan_[4] = {7, 1, 40, 136};
+  wlan_softmac_passive_scan_args_t passive_scan_args_{
+      .channels_list = channels_to_scan_, .channels_count = 4,
+      // TODO(fxbug.dev/88943): Fill in other fields once support determined.
+  };
 
   // Structure to capture scan results.
   struct ScanResult {
@@ -385,49 +390,52 @@
   } scan_result;
 };
 
-class UmacScanTest : public FakeUcodeCapaTest {
+class PassiveUmacScanTest : public FakeUcodeTest {
  public:
-  UmacScanTest() TA_NO_THREAD_SAFETY_ANALYSIS
-      : FakeUcodeCapaTest(0, BIT(IWL_UCODE_TLV_CAPA_UMAC_SCAN)) {
+  PassiveUmacScanTest() __TA_NO_THREAD_SAFETY_ANALYSIS
+      : FakeUcodeTest(0, BIT(IWL_UCODE_TLV_CAPA_UMAC_SCAN), 0, 0) {
     mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans());
     mvmvif_ = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
     mvmvif_->mvm = mvm_;
-    mvmvif_->mac_role = WLAN_INFO_MAC_ROLE_CLIENT;
-    mvmvif_->ifc.ops = reinterpret_cast<wlanmac_ifc_protocol_ops_t*>(
-        calloc(1, sizeof(wlanmac_ifc_protocol_ops_t)));
+    mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
+    mvmvif_->ifc.ops = reinterpret_cast<wlan_softmac_ifc_protocol_ops_t*>(
+        calloc(1, sizeof(wlan_softmac_ifc_protocol_ops_t)));
     mvm_->mvmvif[0] = mvmvif_;
     mvm_->vif_count++;
 
     mtx_lock(&mvm_->mutex);
 
     // Fake callback registered to capture scan completion responses.
-    ops_.hw_scan_complete = [](void* ctx, const wlan_hw_scan_result_t* result) {
+    ops_.scan_complete = [](void* ctx, zx_status_t status, uint64_t scan_id) {
+      // TODO(fxbug.dev/88934): scan_id is always 0
+      EXPECT_EQ(scan_id, 0);
       struct ScanResult* sr = (struct ScanResult*)ctx;
       sr->sme_notified = true;
-      sr->success = (result->code == WLAN_HW_SCAN_SUCCESS ? true : false);
+      sr->success = (status == ZX_OK ? true : false);
     };
 
     mvmvif_sta_.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
-    mvmvif_sta_.mac_role = WLAN_INFO_MAC_ROLE_CLIENT;
+    mvmvif_sta_.mac_role = WLAN_MAC_ROLE_CLIENT;
     mvmvif_sta_.ifc.ops = &ops_;
     mvmvif_sta_.ifc.ctx = &scan_result_;
 
-    // This can be moved out or overridden when we add other scan types.
-    scan_config_.scan_type = WLAN_HW_SCAN_TYPE_PASSIVE;
-
     trans_ = sim_trans_.iwl_trans();
   }
 
-  ~UmacScanTest() TA_NO_THREAD_SAFETY_ANALYSIS {
+  ~PassiveUmacScanTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
     free(mvmvif_->ifc.ops);
     free(mvmvif_);
     mtx_unlock(&mvm_->mutex);
   }
 
   struct iwl_trans* trans_;
-  wlanmac_ifc_protocol_ops_t ops_;
+  wlan_softmac_ifc_protocol_ops_t ops_;
   struct iwl_mvm_vif mvmvif_sta_;
-  wlan_hw_scan_config_t scan_config_{.num_channels = 4, .channels = {7, 1, 40, 136}};
+  uint8_t channels_to_scan_[4] = {7, 1, 40, 136};
+  wlan_softmac_passive_scan_args_t passive_scan_args_{
+      .channels_list = channels_to_scan_, .channels_count = 4,
+      // TODO(fxbug.dev/89693): iwlwifi ignores all other fields.
+  };
 
   // Structure to capture scan results.
   struct ScanResult {
@@ -442,13 +450,13 @@
 
 /* Tests for LMAC scan */
 // Tests scenario for a successful scan completion.
-TEST_F(ScanTest, RegPassiveLmacScanSuccess) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveScanTest, RegPassiveLmacScanSuccess) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
   ASSERT_EQ(false, scan_result.sme_notified);
   ASSERT_EQ(false, scan_result.success);
 
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
 
@@ -468,13 +476,13 @@
 }
 
 // Tests scenario where the scan request aborted / failed.
-TEST_F(ScanTest, RegPassiveLmacScanAborted) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveScanTest, RegPassiveLmacScanAborted) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
 
   ASSERT_EQ(false, scan_result.sme_notified);
   ASSERT_EQ(false, scan_result.success);
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
 
@@ -496,13 +504,12 @@
 
 /* Tests for UMAC scan */
 // Tests scenario for a successful scan completion.
-TEST_F(UmacScanTest, RegPassiveUmacScanSuccess) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveUmacScanTest, RegPassiveUmacScanSuccess) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
   ASSERT_EQ(false, scan_result_.sme_notified);
   ASSERT_EQ(false, scan_result_.success);
-
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta_, &scan_config_));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta_, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta_, mvm_->scan_vif);
 
@@ -522,13 +529,13 @@
 }
 
 // Tests scenario where the scan request aborted / failed.
-TEST_F(UmacScanTest, RegPassiveUmacScanAborted) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveUmacScanTest, RegPassiveUmacScanAborted) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
 
   ASSERT_EQ(false, scan_result_.sme_notified);
   ASSERT_EQ(false, scan_result_.success);
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta_, &scan_config_));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta_, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta_, mvm_->scan_vif);
 
@@ -550,13 +557,13 @@
 
 /* Tests for both LMAC and UMAC scans */
 // Tests condition where scan completion timeouts out due to no response from FW.
-TEST_F(ScanTest, RegPassiveScanTimeout) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveScanTest, RegPassiveScanTimeout) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
 
   ASSERT_EQ(false, scan_result.sme_notified);
   ASSERT_EQ(false, scan_result.success);
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
 
@@ -571,13 +578,13 @@
 }
 
 // Tests condition where timer is shutdown and there is no response from FW.
-TEST_F(ScanTest, RegPassiveScanTimerShutdown) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveScanTest, RegPassiveScanTimerShutdown) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
 
   ASSERT_EQ(false, scan_result.sme_notified);
   ASSERT_EQ(false, scan_result.success);
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
 
@@ -591,11 +598,11 @@
 }
 
 // Tests condition where iwl_mvm_mac_stop() is invoked while timer is pending.
-TEST_F(ScanTest, RegPassiveScanTimerMvmStop) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveScanTest, RegPassiveScanTimerMvmStop) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   ASSERT_EQ(nullptr, mvm_->scan_vif);
 
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
   EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
 
@@ -605,11 +612,11 @@
 }
 
 // Tests condition where multiple calls to the scan API returns appropriate error.
-TEST_F(ScanTest, RegPassiveScanParallel) TA_NO_THREAD_SAFETY_ANALYSIS {
+TEST_F(PassiveScanTest, RegPassiveScanParallel) __TA_NO_THREAD_SAFETY_ANALYSIS {
   ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
-  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
   EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
-  EXPECT_EQ(ZX_ERR_SHOULD_WAIT, iwl_mvm_reg_scan_start(&mvmvif_sta, &scan_config));
+  EXPECT_EQ(ZX_ERR_SHOULD_WAIT, iwl_mvm_reg_scan_start_passive(&mvmvif_sta, &passive_scan_args_));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -625,12 +632,53 @@
 
 TEST_F(TimeEventTest, NormalCase) {
   // wait_for_notif is true.
+  ASSERT_EQ(0, list_length(&mvm_->time_event_list));
   ASSERT_EQ(ZX_OK, iwl_mvm_protect_session(mvm_, mvmvif_, 1, 2, 3, true));
+  ASSERT_EQ(1, list_length(&mvm_->time_event_list));
   ASSERT_EQ(ZX_OK, iwl_mvm_stop_session_protection(mvmvif_));
+  ASSERT_EQ(0, list_length(&mvm_->time_event_list));
 
   // wait_for_notif is false.
+  ASSERT_EQ(0, list_length(&mvm_->time_event_list));
   ASSERT_EQ(ZX_OK, iwl_mvm_protect_session(mvm_, mvmvif_, 1, 2, 3, false));
+  ASSERT_EQ(1, list_length(&mvm_->time_event_list));
   ASSERT_EQ(ZX_OK, iwl_mvm_stop_session_protection(mvmvif_));
+  ASSERT_EQ(0, list_length(&mvm_->time_event_list));
+}
+
+TEST_F(TimeEventTest, Notification) {
+  // Set wait_for_notif to false so that we don't wait for TIME_EVENT_NOTIFICATION.
+  ASSERT_EQ(ZX_OK, iwl_mvm_protect_session(mvm_, mvmvif_, 1, 2, 3, false));
+
+  // On the real device, 'te_data->uid' is populated by response of TIME_EVENT_CMD. However, the
+  // iwl_mvm_time_event_send_add() uses iwl_wait_notification() to get the value instead of reading
+  // from the cmd->resp_pkt (see the comment in iwl_mvm_time_event_send_add()).
+  //
+  // However, the current test/sim-mvm.cc is hard to implement the wait notification yet (which
+  // requires multi-threading model). So, the hack is inserting the 'te_data->uid' in the test code.
+  //
+  // TODO(fxbug.dev/87974): remove this hack once the wait notification model is supported in the
+  //                        testing code.
+  //
+  ASSERT_EQ(1, list_length(&mvm_->time_event_list));
+  auto* te_data = list_peek_head_type(&mvm_->time_event_list, struct iwl_mvm_time_event_data, list);
+  te_data->uid = kFakeUniqueId;
+
+  // Generate a fake TIME_EVENT_NOTIFICATION from the firmware. Note that this notification is
+  // differnt from the above code, which is the notification for TIME_EVENT_CMD.
+  //
+  // We expect the driver will remove the waiting notification from the `time_event_list`.
+  //
+  // TODO(fxbug.dev/51671): remove this hack once the test/sim-mvm.cc can support filing another
+  //                        notification from one host command.
+  //
+  struct iwl_time_event_notif notif = {
+      .unique_id = kFakeUniqueId,
+      .action = TE_V2_NOTIF_HOST_EVENT_END,
+  };
+  TestRxcb time_event_rxcb(sim_trans_.iwl_trans()->dev, &notif, sizeof(notif));
+  iwl_mvm_rx_time_event_notif(mvm_, &time_event_rxcb);
+  ASSERT_EQ(0, list_length(&mvm_->time_event_list));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -721,14 +769,15 @@
         } {
     BIND_TEST(mvm_->trans);
 
-    for (size_t i = 0; i < ARRAY_SIZE(sta_.txq); ++i) {
+    mvm_->fw_id_to_mac_id[0] = &sta_;
+    for (size_t i = 0; i < std::size(sta_.txq); ++i) {
       sta_.txq[i] = reinterpret_cast<struct iwl_mvm_txq*>(calloc(1, sizeof(struct iwl_mvm_txq)));
       ASSERT_NE(nullptr, sta_.txq[i]);
     }
   }
 
   ~TxqTest() {
-    for (size_t i = 0; i < ARRAY_SIZE(sta_.txq); ++i) {
+    for (size_t i = 0; i < std::size(sta_.txq); ++i) {
       free(sta_.txq[i]);
     }
   }
@@ -805,7 +854,7 @@
   iwl_tx_cmd tx_cmd = {
       .tx_flags = TX_CMD_FLG_TSF,  // arbitary value to ensure the function would keep it.
   };
-  iwl_mvm_set_tx_cmd(mvmvif_->mvm, &pkt, &tx_cmd, sta_.sta_id);
+  iwl_mvm_set_tx_cmd(mvmvif_->mvm, &pkt, &tx_cmd, static_cast<uint8_t>(sta_.sta_id));
 
   // Currently the function doesn't consider the QoS so that those values are just fixed value.
   EXPECT_EQ(TX_CMD_FLG_TSF | TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_BT_DIS | TX_CMD_FLG_ACK,
@@ -820,15 +869,40 @@
 
 TEST_F(TxqTest, DataTxCmdRate) {
   iwl_tx_cmd tx_cmd = {};
-  iwl_mvm_set_tx_cmd_rate(mvmvif_->mvm, &tx_cmd);
+  struct ieee80211_frame_header frame_hdr;
+  // construct a data frame, and check the rate.
+  frame_hdr.frame_ctrl |= IEEE80211_FRAME_TYPE_DATA;
+  sta_.sta_state = IWL_STA_AUTHORIZED;
+
+  iwl_mvm_set_tx_cmd_rate(mvmvif_->mvm, &tx_cmd, &frame_hdr);
+
+  // Verify tx_cmd rate fields when frame type is data frame when station is authorized, the rate
+  // should not be set.
+  EXPECT_EQ(0, tx_cmd.initial_rate_index);
+  EXPECT_GT(tx_cmd.tx_flags & cpu_to_le32(TX_CMD_FLG_STA_RATE), 0);
+  EXPECT_EQ(0, tx_cmd.rate_n_flags);
 
   EXPECT_EQ(IWL_RTS_DFAULT_RETRY_LIMIT, tx_cmd.rts_retry_limit);
-  EXPECT_EQ(IWL_RATE_6M_PLCP | BIT(mvm_->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS,
-            tx_cmd.rate_n_flags);
   EXPECT_EQ(IWL_DEFAULT_TX_RETRY, tx_cmd.data_retry_limit);
 }
 
-TEST_F(TxqTest, TxpktInvalidInput) {
+TEST_F(TxqTest, MgmtTxCmdRate) {
+  iwl_tx_cmd tx_cmd = {};
+  struct ieee80211_frame_header frame_hdr;
+
+  // construct a non-data frame, and check the rate.
+  frame_hdr.frame_ctrl |= IEEE80211_FRAME_TYPE_MGMT;
+
+  iwl_mvm_set_tx_cmd_rate(mvmvif_->mvm, &tx_cmd, &frame_hdr);
+
+  // Because the rate which is set to non-data frame in our code is a temporary value, so this line
+  // might be changed in the future.
+  EXPECT_EQ(iwl_mvm_mac80211_idx_to_hwrate(IWL_FIRST_OFDM_RATE) |
+                (BIT(mvm_->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS),
+            tx_cmd.rate_n_flags);
+}
+
+TEST_F(TxqTest, TxPktInvalidInput) {
   WlanPktBuilder builder;
   std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt(builder.build());
 
@@ -865,6 +939,33 @@
   unbindTx();
 }
 
+// Check to see Tx params are set correctly based on frame control
+TEST_F(TxqTest, TxPktProtected) {
+  // Send a protected data frame and see that the crypt header is being added
+  WlanPktBuilder builder;
+  std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt(builder.build(0x4188));
+
+  EXPECT_EQ(wlan_pkt->mac_pkt()->headroom_used_size, 0);
+  // Setup a key conf to pretend that this is a secure connection
+  auto key_conf = reinterpret_cast<ieee80211_key_conf*>(malloc(sizeof(ieee80211_key_conf) + 16));
+  memset(key_conf, 0, sizeof(*key_conf) + 16);
+  key_conf->cipher = 4;
+  key_conf->key_type = 1;
+  key_conf->keyidx = 0;
+  key_conf->keylen = 16;
+  key_conf->rx_seq = 0;
+  wlan_pkt->mac_pkt()->info.control.hw_key = key_conf;
+
+  bindTx(tx_wrapper);
+  // Expect the packet length to be 8 bytes longer
+  mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len() + 8, WIDE_ID(0, TX_CMD), 0);
+  EXPECT_EQ(ZX_OK, iwl_mvm_tx_skb(mvmvif_->mvm, wlan_pkt->mac_pkt(), &sta_));
+  unbindTx();
+  // Expect that the headroom size is set to 8
+  EXPECT_EQ(wlan_pkt->mac_pkt()->headroom_used_size, 8);
+  free(key_conf);
+}
+
 }  // namespace
 }  // namespace testing
 }  // namespace wlan
diff --git a/third_party/iwlwifi/test/notif-wait-test.cc b/third_party/iwlwifi/test/notif-wait-test.cc
index 9405610..448d089 100644
--- a/third_party/iwlwifi/test/notif-wait-test.cc
+++ b/third_party/iwlwifi/test/notif-wait-test.cc
@@ -8,6 +8,8 @@
 // IRQ) and the user (the driver code waiting for the notification from firmware). In this file,
 // we will act as those 2 parties in a test case.
 
+#include <iterator>
+
 #include <zxtest/zxtest.h>
 
 extern "C" {
@@ -49,7 +51,7 @@
 static void helper_create_fake_cmd(struct iwl_notif_wait_data* wait_data,
                                    struct iwl_notification_wait* wait_entry, uint16_t fake_cmd) {
   uint16_t cmds[] = {fake_cmd};
-  iwl_init_notification_wait(wait_data, wait_entry, cmds, countof(cmds), nullptr, nullptr);
+  iwl_init_notification_wait(wait_data, wait_entry, cmds, std::size(cmds), nullptr, nullptr);
 }
 
 // Helper function for test case init. This would create a wait_entry for fake cmd 1.
@@ -220,7 +222,7 @@
   iwl_notification_wait_init(wait_data);
 
   uint16_t cmds[] = {FAKE_CMD_1};
-  iwl_init_notification_wait(wait_data, wait_entry, cmds, countof(cmds), fn, fn_data);
+  iwl_init_notification_wait(wait_data, wait_entry, cmds, std::size(cmds), fn, fn_data);
 }
 
 TEST_F(NotifWaitTest, FnReturnsTrue) {
diff --git a/third_party/iwlwifi/test/nvm-test.cc b/third_party/iwlwifi/test/nvm-test.cc
index deca6e9..f04437a 100644
--- a/third_party/iwlwifi/test/nvm-test.cc
+++ b/third_party/iwlwifi/test/nvm-test.cc
@@ -11,14 +11,14 @@
 }
 
 #include "third_party/iwlwifi/iwl-drv.h"
-#include "third_party/iwlwifi/test/single-ap-test.h"
+#include "third_party/iwlwifi/test/fake-ucode-test.h"
 
 namespace wlan::testing {
 namespace {
 
-class NvmTest : public SingleApTest {
+class NvmTest : public FakeUcodeTest {
  public:
-  NvmTest() {}
+  NvmTest() : FakeUcodeTest(0, BIT(IWL_UCODE_TLV_CAPA_LAR_SUPPORT), 0, 0) {}
   ~NvmTest() {}
 };
 
@@ -48,18 +48,40 @@
 
   // Band and channel info
   // - 2G band
-  EXPECT_EQ(data->bands[WLAN_INFO_BAND_2GHZ].band, WLAN_INFO_BAND_2GHZ);
-  EXPECT_EQ(data->bands[WLAN_INFO_BAND_2GHZ].n_channels, 13);
-  EXPECT_EQ(data->bands[WLAN_INFO_BAND_2GHZ].channels[0].ch_num, 1);
+  EXPECT_EQ(data->bands[WLAN_INFO_BAND_TWO_GHZ].band, WLAN_INFO_BAND_TWO_GHZ);
+  EXPECT_EQ(data->bands[WLAN_INFO_BAND_TWO_GHZ].n_channels, 14);
+  EXPECT_EQ(data->bands[WLAN_INFO_BAND_TWO_GHZ].channels[0].ch_num, 1);
   // - 5G band
-  EXPECT_EQ(data->bands[WLAN_INFO_BAND_5GHZ].band, WLAN_INFO_BAND_5GHZ);
-  EXPECT_EQ(data->bands[WLAN_INFO_BAND_5GHZ].n_channels, 25);
-  EXPECT_EQ(data->bands[WLAN_INFO_BAND_5GHZ].channels[0].ch_num, 36);
+  EXPECT_EQ(data->bands[WLAN_INFO_BAND_FIVE_GHZ].band, WLAN_INFO_BAND_FIVE_GHZ);
+  EXPECT_EQ(data->bands[WLAN_INFO_BAND_FIVE_GHZ].n_channels, 25);
+  EXPECT_EQ(data->bands[WLAN_INFO_BAND_FIVE_GHZ].channels[0].ch_num, 36);
 }
 
 TEST_F(NvmTest, CfgRatesTo80211) {
   EXPECT_EQ(2, cfg_rates_to_80211(1 * 10));  // 1 Mbps
 }
 
+TEST_F(NvmTest, UpdateMcc) {
+  auto mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
+  struct iwl_mcc_update_resp *resp;
+
+  mtx_lock(&mvm->mutex);
+  EXPECT_EQ(ZX_OK, iwl_mvm_update_mcc(mvm, "US", MCC_SOURCE_WIFI, &resp));
+  mtx_unlock(&mvm->mutex);
+
+  EXPECT_EQ(resp->mcc, 0x5553);  // "US"
+
+  free(resp);
+}
+
+TEST_F(NvmTest, InitMcc) {
+  auto mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
+
+  mtx_lock(&mvm->mutex);
+  EXPECT_EQ(ZX_OK, iwl_mvm_init_mcc(mvm));
+  mtx_unlock(&mvm->mutex);
+  EXPECT_EQ(true, mvm->lar_regdom_set);
+}
+
 }  // namespace
 }  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/pcie-test.cc b/third_party/iwlwifi/test/pcie-test.cc
index dd8c396..c2a5b35 100644
--- a/third_party/iwlwifi/test/pcie-test.cc
+++ b/third_party/iwlwifi/test/pcie-test.cc
@@ -31,18 +31,18 @@
 #include "src/devices/testing/mock-ddk/mock-device.h"
 
 extern "C" {
-#include "fw/api/commands.h"
-#include "iwl-csr.h"
-#include "pcie/internal.h"
+#include "third_party/iwlwifi/fw/api/commands.h"
+#include "third_party/iwlwifi/iwl-csr.h"
+#include "third_party/iwlwifi/pcie/internal.h"
 }
 
-#include "align.h"
-#include "platform/ieee80211.h"
-#include "platform/irq.h"
-#include "platform/kernel.h"
-#include "platform/memory.h"
-#include "platform/pcie-device.h"
-#include "test/wlan-pkt-builder.h"
+#include "third_party/iwlwifi/platform/align.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/irq.h"
+#include "third_party/iwlwifi/platform/kernel.h"
+#include "third_party/iwlwifi/platform/memory.h"
+#include "third_party/iwlwifi/platform/pcie-device.h"
+#include "third_party/iwlwifi/test/wlan-pkt-builder.h"
 #include "src/devices/pci/testing/pci_protocol_fake.h"
 
 namespace {
@@ -333,7 +333,7 @@
     for (uint32_t i = from; i <= to; ++i) {
       struct iwl_rx_mem_buffer* rxb = rxq->queue[i];
       for (size_t offset = 0; offset < iwl_iobuf_size(rxb->io_buf);
-           offset += ZX_ALIGN(1, FH_RSCSR_FRAME_ALIGN)) {  // move to next packet
+           offset += IWL_ALIGN(1, FH_RSCSR_FRAME_ALIGN)) {  // move to next packet
         struct iwl_rx_cmd_buffer rxcb = {
             ._iobuf = rxb->io_buf,
             ._offset = static_cast<int>(offset),
@@ -479,7 +479,7 @@
   EXPECT_EQ(trans_pcie_->ict_index, 4);
 
   // This should match ICT_COUNT defined in pcie/rx.c.
-  size_t ict_count = iwl_iobuf_size(trans_pcie_->ict_tbl) / sizeof(uint32_t);
+  int ict_count = static_cast<int>(iwl_iobuf_size(trans_pcie_->ict_tbl) / sizeof(uint32_t));
 
   // Guarantee that we have enough room in the table for the tests.
   ASSERT_GT(ict_count, 42);
diff --git a/third_party/iwlwifi/test/phy-ctxt-test.cc b/third_party/iwlwifi/test/phy-ctxt-test.cc
index adf93a4..4cb8c32 100644
--- a/third_party/iwlwifi/test/phy-ctxt-test.cc
+++ b/third_party/iwlwifi/test/phy-ctxt-test.cc
@@ -4,7 +4,7 @@
 
 // Used to test mvm/phy-ctxt.c
 
-#include <lib/zircon-internal/thread_annotations.h>
+#include <zircon/compiler.h>
 
 #include <zxtest/zxtest.h>
 
@@ -20,11 +20,11 @@
 
 class PhyContextTest : public SingleApTest {
  public:
-  PhyContextTest() TA_NO_THREAD_SAFETY_ANALYSIS {
+  PhyContextTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
     mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans());
     mtx_lock(&mvm_->mutex);
   }
-  ~PhyContextTest() TA_NO_THREAD_SAFETY_ANALYSIS { mtx_unlock(&mvm_->mutex); }
+  ~PhyContextTest() __TA_NO_THREAD_SAFETY_ANALYSIS { mtx_unlock(&mvm_->mutex); }
 
  protected:
   struct iwl_mvm* mvm_;
diff --git a/third_party/iwlwifi/test/platform-test.cc b/third_party/iwlwifi/test/platform-test.cc
index 6435a23..311693c 100644
--- a/third_party/iwlwifi/test/platform-test.cc
+++ b/third_party/iwlwifi/test/platform-test.cc
@@ -129,6 +129,49 @@
   EXPECT_EQ(32, find_first_bit(test2, 64));
 }
 
+TEST_F(PlatformTest, find_last_bit) {
+  unsigned test0[] = {
+      0x00000000,
+      0x00000000,
+  };
+  EXPECT_EQ(1, find_last_bit(test0, 1));    // Not found
+  EXPECT_EQ(31, find_last_bit(test0, 31));  // Not found
+  EXPECT_EQ(32, find_last_bit(test0, 32));  // Not found
+  EXPECT_EQ(64, find_last_bit(test0, 64));  // Not found
+
+  unsigned test1[] = {
+      0x40000000,
+      0x00010001,
+  };
+  ASSERT_EQ(sizeof(*test1), 4);
+  EXPECT_EQ(1, find_last_bit(test1, 1));  // Not found
+  EXPECT_EQ(30, find_last_bit(test1, 31));
+  EXPECT_EQ(30, find_last_bit(test1, 32));
+  EXPECT_EQ(32, find_last_bit(test1, 33));
+  EXPECT_EQ(32, find_last_bit(test1, 48));
+  EXPECT_EQ(48, find_last_bit(test1, 64));
+
+  unsigned test2[] = {
+      0x00000000,
+      0x80000000,
+  };
+  EXPECT_EQ(31, find_last_bit(test2, 31));  // Not found
+  EXPECT_EQ(32, find_last_bit(test2, 32));  // Not found
+  EXPECT_EQ(33, find_last_bit(test2, 33));  // Not found
+  EXPECT_EQ(63, find_last_bit(test2, 64));
+}
+
+TEST_F(PlatformTest, find_next_bit) {
+  unsigned test[] = {
+      0x00001000,
+      0x00000000,
+  };
+
+  EXPECT_EQ(32, find_next_bit(test, 32, 16));  // Not found
+  EXPECT_EQ(12, find_next_bit(test, 32, 0));
+  EXPECT_EQ(12, find_next_bit(test, 64, 0));
+}
+
 TEST_F(PlatformTest, HexDumpErrorHandling) {
   char buf[HEX_DUMP_BUF_SIZE - 1];
   uint8_t data[] = {};
diff --git a/third_party/iwlwifi/test/rcu-manager-test.cc b/third_party/iwlwifi/test/rcu-manager-test.cc
new file mode 100644
index 0000000..aeb2f82
--- /dev/null
+++ b/third_party/iwlwifi/test/rcu-manager-test.cc
@@ -0,0 +1,115 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/platform/rcu-manager.h"
+
+#include <lib/async-testing/test_loop.h>
+#include <lib/sync/completion.h>
+#include <zircon/time.h>
+
+#include <memory>
+#include <thread>
+
+#include <zxtest/zxtest.h>
+
+namespace wlan::testing {
+namespace {
+
+TEST(RcuManagerTest, NestedReadLock) {
+  auto test_loop = std::make_unique<::async::TestLoop>();
+  auto manager = std::make_unique<::wlan::iwlwifi::RcuManager>(test_loop->dispatcher());
+
+  manager->InitForThread();
+  manager->ReadLock();
+
+  // Call RcuManager::Sync() on another thread.
+  bool synced = false;
+  std::thread sync_thread([&]() {
+    manager->InitForThread();
+    manager->Sync();
+    synced = true;
+  });
+  EXPECT_FALSE(synced);
+
+  // Nesting a lock and then unlocking it (without unlocking the outer lock) should not cause the
+  // Sync() call to complete yet.
+  manager->ReadLock();
+  EXPECT_FALSE(synced);
+  manager->ReadUnlock();
+  EXPECT_FALSE(synced);
+
+  // Unlocking the lock a final time should allow the Sync() call to finish.
+  manager->ReadUnlock();
+  sync_thread.join();
+  EXPECT_TRUE(synced);
+}
+
+TEST(RcuManagerTest, ThreadedReadLock) {
+  auto test_loop = std::make_unique<::async::TestLoop>();
+  auto manager = std::make_unique<::wlan::iwlwifi::RcuManager>(test_loop->dispatcher());
+
+  manager->InitForThread();
+  manager->ReadLock();
+
+  // Perform a read-side lock on another thread.
+  sync_completion_t thread_started = {};
+  sync_completion_t thread_continue = {};
+  std::thread lock_thread([&]() {
+    manager->InitForThread();
+    manager->ReadLock();
+    sync_completion_signal(&thread_started);
+    sync_completion_wait(&thread_continue, ZX_TIME_INFINITE);
+    manager->ReadUnlock();
+  });
+
+  // Wait for the other thread to read-side lock.
+  sync_completion_wait(&thread_started, ZX_TIME_INFINITE);
+
+  // Start a thread that waits for a sync.
+  bool synced = false;
+  std::thread sync_thread([&]() {
+    manager->InitForThread();
+    manager->Sync();
+    synced = true;
+  });
+  EXPECT_FALSE(synced);
+
+  // Unlocking this thread still means another thread is holding the read-side lock.
+  manager->ReadUnlock();
+  EXPECT_FALSE(synced);
+
+  // Unlocking the other thread finally allows the Sync() to complete.
+  sync_completion_signal(&thread_continue);
+  lock_thread.join();
+  sync_thread.join();
+  EXPECT_TRUE(synced);
+}
+
+TEST(RcuManagerTest, CallSync) {
+  auto test_loop = std::make_unique<::async::TestLoop>();
+  auto manager = std::make_unique<::wlan::iwlwifi::RcuManager>(test_loop->dispatcher());
+
+  manager->InitForThread();
+  manager->ReadLock();
+
+  // Prepare a call that will execute after all read-side locks are unlocked.
+  bool called = false;
+  manager->CallSync([](void* data) { *reinterpret_cast<bool*>(data) = true; }, &called);
+  std::thread loop_thread([&]() { test_loop->RunUntilIdle(); });
+  EXPECT_FALSE(called);
+
+  // A nested lock will not cause the call to executed.
+  manager->ReadLock();
+  EXPECT_FALSE(called);
+  manager->ReadUnlock();
+  EXPECT_FALSE(called);
+
+  // Unlocking the last lock allows the call to proceed.
+  manager->ReadUnlock();
+  loop_thread.join();
+  EXPECT_TRUE(called);
+}
+
+}  // namespace
+}  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/sim-mcc-update.cc b/third_party/iwlwifi/test/sim-mcc-update.cc
new file mode 100644
index 0000000..0464f73
--- /dev/null
+++ b/third_party/iwlwifi/test/sim-mcc-update.cc
@@ -0,0 +1,29 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/test/sim-mcc-update.h"
+
+#include <string.h>
+#include <zircon/assert.h>
+
+extern "C" {
+#include "third_party/iwlwifi/mvm/mvm.h"
+}
+
+namespace wlan::testing {
+
+zx_status_t HandleMccUpdate(struct iwl_host_cmd* cmd, SimMvmResponse* resp) {
+  auto mcc_cmd = reinterpret_cast<const struct iwl_mcc_update_cmd*>(cmd->data[0]);
+
+  resp->resize(sizeof(struct iwl_mcc_update_resp_v3));
+  auto mcc_resp = reinterpret_cast<struct iwl_mcc_update_resp_v3*>(resp->data());
+  memset(mcc_resp, 0, sizeof(*mcc_resp));
+  mcc_resp->status = le32_to_cpu(MCC_RESP_NEW_CHAN_PROFILE);
+  mcc_resp->mcc = mcc_cmd->mcc;
+  mcc_resp->source_id = mcc_cmd->source_id;
+
+  return ZX_OK;
+}
+
+}  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/sim-mcc-update.h b/third_party/iwlwifi/test/sim-mcc-update.h
new file mode 100644
index 0000000..fb87ade
--- /dev/null
+++ b/third_party/iwlwifi/test/sim-mcc-update.h
@@ -0,0 +1,21 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// To simulate the TIME_EVENT_CMD in the testing code.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_SIM_MCC_UPDATE_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_SIM_MCC_UPDATE_H_
+
+#include <zircon/types.h>
+
+#include "third_party/iwlwifi/iwl-trans.h"
+#include "third_party/iwlwifi/test/sim.h"
+
+namespace wlan::testing {
+
+zx_status_t HandleMccUpdate(struct iwl_host_cmd* cmd, SimMvmResponse* resp);
+
+}  // namespace wlan::testing
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_SIM_MCC_UPDATE_H_
diff --git a/third_party/iwlwifi/test/sim-mvm.cc b/third_party/iwlwifi/test/sim-mvm.cc
index ac79f6e..7a1cc1e 100644
--- a/third_party/iwlwifi/test/sim-mvm.cc
+++ b/third_party/iwlwifi/test/sim-mvm.cc
@@ -4,6 +4,9 @@
 
 #include "third_party/iwlwifi/test/sim-mvm.h"
 
+#include "third_party/iwlwifi/test/sim-mcc-update.h"
+#include "third_party/iwlwifi/test/sim-time-event.h"
+
 extern "C" {
 #include "third_party/iwlwifi/fw/api/commands.h"
 #include "third_party/iwlwifi/fw/api/datapath.h"
@@ -40,7 +43,8 @@
         // On real hardware, this command will reply a pakcet to unblock the driver waiting.
         // In the simulated code, we don't generate the packet. Instead, we unblock it directly.
         case PHY_CONFIGURATION_CMD:
-          // passthru
+          *notify_wait = true;
+          return ZX_OK;
 
         // The driver code expects 2 notifications from the firmware in this command:
         //
@@ -51,11 +55,11 @@
         // However, the current sim-mvm code can only unblock the first wait. So added
         // TODO(fxbug.dev/51671) to track this.
         case TIME_EVENT_CMD:
-          // passthru
-
-          // The above commands require unblock the notification.
+          ret = HandleTimeEvent(cmd, &resp);
           *notify_wait = true;
-          __FALLTHROUGH;
+          break;
+
+        // Below commands don't require a response in the testing code to continue.
         case SHARED_MEM_CFG:
         case TX_ANT_CONFIGURATION_CMD:
         case PHY_DB_CMD:
@@ -65,6 +69,7 @@
         case BT_CONFIG:
         case MAC_CONTEXT_CMD:
         case TXPATH_FLUSH:
+        case LQ_CMD:
         case SCAN_OFFLOAD_REQUEST_CMD:
         case SCAN_CFG_CMD:
         case SCAN_REQ_UMAC:
@@ -76,7 +81,7 @@
         case REPLY_BEACON_FILTERING_CMD:
           return ZX_OK;
 
-        // Command would return 'status' back to driver.
+        // Commands would return 'status' back to driver.
         case BINDING_CONTEXT_CMD:
           build_response_with_status(&resp, 0);
           ret = ZX_OK;
@@ -89,6 +94,10 @@
           ret = ZX_OK;
           break;
 
+        case MCC_UPDATE_CMD:
+          ret = HandleMccUpdate(cmd, &resp);
+          break;
+
         case NVM_ACCESS_CMD:
           ret = nvm_.HandleCommand(cmd, &resp);
           break;
diff --git a/third_party/iwlwifi/test/sim-mvm.h b/third_party/iwlwifi/test/sim-mvm.h
index 01ed86f..eab33da 100644
--- a/third_party/iwlwifi/test/sim-mvm.h
+++ b/third_party/iwlwifi/test/sim-mvm.h
@@ -8,9 +8,6 @@
 #include <memory>
 #include <vector>
 
-#include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-env.h"
-#include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-sta-ifc.h"
-
 extern "C" {
 #include "third_party/iwlwifi/iwl-trans.h"
 }  // extern "C"
@@ -20,16 +17,9 @@
 
 namespace wlan::testing {
 
-class SimMvm : public ::wlan::simulation::StationIfc {
+class SimMvm {
  public:
-  explicit SimMvm(::wlan::simulation::Environment* env) : env_(env), resp_buf_(kNvmAccessCmdSize) {
-    env->AddStation(this);
-  }
-  ~SimMvm() {
-    if (env_ != nullptr) {
-      env_->RemoveStation(this);
-    }
-  }
+  explicit SimMvm() : resp_buf_(kNvmAccessCmdSize) {}
 
   // Execute the command.
   //
@@ -39,16 +29,11 @@
   //
   zx_status_t SendCmd(struct iwl_host_cmd* cmd, bool* notify_wait);
 
-  // StationIfc operations
-  void Rx(std::shared_ptr<const simulation::SimFrame> frame,
-          std::shared_ptr<const simulation::WlanRxInfo> info) override {}
-
  private:
   // The buffer size should be determined by the max response size.
   // This number is for the response of NVM_ACCESS_CMD read command.
   static constexpr size_t kNvmAccessCmdSize = 16 + 2048;
 
-  ::wlan::simulation::Environment* env_;
   SimNvm nvm_;
 
   // Used by SendCmd() to store the response from the simulated firmware functions.
diff --git a/third_party/iwlwifi/test/sim-nvm-data.inc b/third_party/iwlwifi/test/sim-nvm-data.inc
index eaa3a41..fe18d0a 100644
--- a/third_party/iwlwifi/test/sim-nvm-data.inc
+++ b/third_party/iwlwifi/test/sim-nvm-data.inc
@@ -910,7 +910,7 @@
           .data = {},
       }};
   return kDefaultNvmSections;
-};
+}
 
 }  // namespace
 }  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/sim-nvm.cc b/third_party/iwlwifi/test/sim-nvm.cc
index 4c8ad34..529e4d8 100644
--- a/third_party/iwlwifi/test/sim-nvm.cc
+++ b/third_party/iwlwifi/test/sim-nvm.cc
@@ -7,6 +7,7 @@
 #include <string.h>
 #include <zircon/assert.h>
 
+#include <algorithm>
 #include <vector>
 
 extern "C" {
@@ -35,17 +36,11 @@
     }
 
     // Handle the boundary cases.
-    size_t size = iter.data.size();
-    if (offset > size) {
-      offset = size;
-    }
-    if (offset + length > size) {
-      length = size - offset;
-    }
-
+    const size_t read_offset = std::min<size_t>(offset, iter.data.size());
+    const size_t read_length = std::min<size_t>(length, iter.data.size() - read_offset);
     std::vector<uint8_t> ret;
-    ret.reserve(length);
-    std::copy_n(iter.data.begin() + offset, length, std::back_inserter(ret));
+    ret.reserve(read_length);
+    std::copy_n(iter.data.begin() + read_offset, read_length, std::back_inserter(ret));
     return ret;
   }
 
diff --git a/third_party/iwlwifi/test/sim-time-event.cc b/third_party/iwlwifi/test/sim-time-event.cc
new file mode 100644
index 0000000..5744807
--- /dev/null
+++ b/third_party/iwlwifi/test/sim-time-event.cc
@@ -0,0 +1,29 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/test/sim-time-event.h"
+
+#include <string.h>
+#include <zircon/assert.h>
+
+extern "C" {
+#include "third_party/iwlwifi/mvm/mvm.h"
+}
+
+namespace wlan::testing {
+
+zx_status_t HandleTimeEvent(struct iwl_host_cmd* cmd, SimMvmResponse* resp) {
+  auto tm_cmd = reinterpret_cast<const struct iwl_time_event_cmd*>(cmd->data[0]);
+
+  resp->resize(sizeof(struct iwl_time_event_resp));
+  auto tm_resp = reinterpret_cast<struct iwl_time_event_resp*>(resp->data());
+  memset(tm_resp, 0, sizeof(*tm_resp));
+  // Copy the 'id' field from the command and fill a fake value in 'unique_id' field.
+  tm_resp->id = le32_to_cpu(tm_cmd->id);
+  tm_resp->unique_id = le32_to_cpu(kFakeUniqueId);
+
+  return ZX_OK;
+}
+
+}  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/sim-time-event.h b/third_party/iwlwifi/test/sim-time-event.h
new file mode 100644
index 0000000..70cca7b
--- /dev/null
+++ b/third_party/iwlwifi/test/sim-time-event.h
@@ -0,0 +1,25 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// To simulate the TIME_EVENT_CMD in the testing code.
+
+#ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_SIM_TIME_EVENT_H_
+#define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_SIM_TIME_EVENT_H_
+
+#include <zircon/types.h>
+
+#include "third_party/iwlwifi/iwl-trans.h"
+#include "third_party/iwlwifi/test/sim.h"
+
+namespace wlan::testing {
+
+// The returned unique_id for the time event command. On the real firmware, this value is
+// generated randomly. But a fake fixed value is good enough for the testing code.
+constexpr uint16_t kFakeUniqueId = 0x5566;
+
+zx_status_t HandleTimeEvent(struct iwl_host_cmd* cmd, SimMvmResponse* resp);
+
+}  // namespace wlan::testing
+
+#endif  // SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_SIM_TIME_EVENT_H_
diff --git a/third_party/iwlwifi/test/sim-trans.cc b/third_party/iwlwifi/test/sim-trans.cc
index f143a9a..5ed7a22 100644
--- a/third_party/iwlwifi/test/sim-trans.cc
+++ b/third_party/iwlwifi/test/sim-trans.cc
@@ -18,7 +18,7 @@
 
 #include "third_party/iwlwifi/test/sim-trans.h"
 
-#include <fuchsia/hardware/wlan/mac/c/banjo.h>
+#include <fuchsia/hardware/wlan/softmac/c/banjo.h>
 #include <fuchsia/hardware/wlanphyimpl/c/banjo.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/async-loop/default.h>
@@ -35,6 +35,7 @@
 }
 
 #include "third_party/iwlwifi/platform/mvm-mlme.h"
+#include "third_party/iwlwifi/platform/rcu-manager.h"
 #include "third_party/iwlwifi/platform/wlanphy-impl-device.h"
 #include "src/devices/testing/mock-ddk/mock-device.h"
 
@@ -48,7 +49,7 @@
 class SimTransDevice : public ::wlan::iwlwifi::WlanphyImplDevice {
  public:
   explicit SimTransDevice(zx_device_t* parent, iwl_trans* drvdata)
-      : WlanphyImplDevice(parent), drvdata_(drvdata){};
+      : WlanphyImplDevice(parent), drvdata_(drvdata) {}
   void DdkInit(::ddk::InitTxn txn) override { txn.Reply(ZX_OK); }
   void DdkUnbind(::ddk::UnbindTxn txn) override {
     struct iwl_trans* trans = drvdata_;
@@ -57,7 +58,7 @@
     }
     free(trans);
     txn.Reply();
-  };
+  }
 
   iwl_trans* drvdata() override { return drvdata_; }
   const iwl_trans* drvdata() const override { return drvdata_; }
@@ -310,16 +311,17 @@
 
 namespace wlan::testing {
 
-SimTransport::SimTransport(::wlan::simulation::Environment* env, zx_device_t* parent)
-    : SimMvm(env), device_{}, iwl_trans_(nullptr) {
+SimTransport::SimTransport(zx_device_t* parent) : device_{}, iwl_trans_(nullptr) {
   task_loop_ = std::make_unique<::async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
   task_loop_->StartThread("iwlwifi-test-task-worker", nullptr);
   irq_loop_ = std::make_unique<::async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
   irq_loop_->StartThread("iwlwifi-test-irq-worker", nullptr);
+  rcu_manager_ = std::make_unique<::wlan::iwlwifi::RcuManager>(task_loop_->dispatcher());
 
   device_.zxdev = parent;
   device_.task_dispatcher = task_loop_->dispatcher();
   device_.irq_dispatcher = irq_loop_->dispatcher();
+  device_.rcu_manager = static_cast<struct rcu_manager*>(rcu_manager_.get());
   fake_bti_create(&device_.bti);
 }
 
@@ -341,8 +343,8 @@
 
 const struct iwl_trans* SimTransport::iwl_trans() const { return iwl_trans_; }
 
-wlan::iwlwifi::WlanphyImplDevice* SimTransport::sim_device() { return sim_device_; }
+::wlan::iwlwifi::WlanphyImplDevice* SimTransport::sim_device() { return sim_device_; }
 
-const wlan::iwlwifi::WlanphyImplDevice* SimTransport::sim_device() const { return sim_device_; }
+const ::wlan::iwlwifi::WlanphyImplDevice* SimTransport::sim_device() const { return sim_device_; }
 
 }  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/sim-trans.h b/third_party/iwlwifi/test/sim-trans.h
index ce53d30..a368d67 100644
--- a/third_party/iwlwifi/test/sim-trans.h
+++ b/third_party/iwlwifi/test/sim-trans.h
@@ -15,7 +15,6 @@
 
 #include <memory>
 
-#include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-env.h"
 #include "third_party/iwlwifi/platform/kernel.h"
 #include "third_party/iwlwifi/test/sim-mvm.h"
 
@@ -28,6 +27,7 @@
 namespace wlan {
 namespace iwlwifi {
 
+class RcuManager;
 class WlanphyImplDevice;
 
 }  // namespace iwlwifi
@@ -49,7 +49,7 @@
 
 class SimTransport : public SimMvm {
  public:
-  explicit SimTransport(::wlan::simulation::Environment* env, zx_device_t* parent);
+  explicit SimTransport(zx_device_t* parent);
   ~SimTransport();
 
   // This function must be called before starting using other functions.
@@ -58,12 +58,13 @@
   // Member accessors.
   struct iwl_trans* iwl_trans();
   const struct iwl_trans* iwl_trans() const;
-  wlan::iwlwifi::WlanphyImplDevice* sim_device();
-  const wlan::iwlwifi::WlanphyImplDevice* sim_device() const;
+  ::wlan::iwlwifi::WlanphyImplDevice* sim_device();
+  const ::wlan::iwlwifi::WlanphyImplDevice* sim_device() const;
 
  private:
   std::unique_ptr<::async::Loop> task_loop_;
   std::unique_ptr<::async::Loop> irq_loop_;
+  std::unique_ptr<::wlan::iwlwifi::RcuManager> rcu_manager_;
   struct device device_;
   struct iwl_trans* iwl_trans_;
   wlan::iwlwifi::WlanphyImplDevice* sim_device_;
diff --git a/third_party/iwlwifi/test/single-ap-test.cc b/third_party/iwlwifi/test/single-ap-test.cc
index db98be1..950b47c 100644
--- a/third_party/iwlwifi/test/single-ap-test.cc
+++ b/third_party/iwlwifi/test/single-ap-test.cc
@@ -17,9 +17,7 @@
 const common::MacAddr SingleApTest::default_macaddr_(kApAddr);
 
 SingleApTest::SingleApTest()
-    : fake_parent_(MockDevice::FakeRootParent()),
-      ap_(&env_, default_macaddr_, kSsid, kChannel),
-      sim_trans_(&env_, fake_parent_.get()) {
+    : fake_parent_(MockDevice::FakeRootParent()), sim_trans_(fake_parent_.get()) {
   // Add a default MVM firmware to the fake DDK.
   TlvFwBuilder fw_builder;
 
@@ -40,7 +38,6 @@
   zx_status_t status = sim_trans_.Init();
   ZX_ASSERT_MSG(ZX_OK == status, "Transportation initialization failed: %s",
                 zx_status_get_string(status));
-  env_.AddStation(&ap_);
 }
 
 SingleApTest::~SingleApTest() = default;
diff --git a/third_party/iwlwifi/test/single-ap-test.h b/third_party/iwlwifi/test/single-ap-test.h
index 3b48dcc..25ddbea 100644
--- a/third_party/iwlwifi/test/single-ap-test.h
+++ b/third_party/iwlwifi/test/single-ap-test.h
@@ -9,10 +9,9 @@
 
 #include <memory>
 
+#include <wlan/common/macaddr.h>
 #include <zxtest/zxtest.h>
 
-#include "src/connectivity/wlan/drivers/testing/lib/sim-env/sim-env.h"
-#include "src/connectivity/wlan/drivers/testing/lib/sim-fake-ap/sim-fake-ap.h"
 #include "third_party/iwlwifi/test/sim-trans.h"
 #include "src/devices/testing/mock-ddk/mock-device.h"
 
@@ -38,8 +37,6 @@
   static const common::MacAddr default_macaddr_;
 
   std::shared_ptr<MockDevice> fake_parent_;
-  ::wlan::simulation::Environment env_;
-  ::wlan::simulation::FakeAp ap_;
   SimTransport sim_trans_;
 };
 
diff --git a/third_party/iwlwifi/test/stub-mvm.cc b/third_party/iwlwifi/test/stub-mvm.cc
index 10c60c7..128aebd 100644
--- a/third_party/iwlwifi/test/stub-mvm.cc
+++ b/third_party/iwlwifi/test/stub-mvm.cc
@@ -5,9 +5,6 @@
 // When MVM isn't otherwise linked into a test, we will need to provide implementations for some of
 // its entry points that are not called but still linked in.
 
-// TODO(rsakthi) - how to get this from bazel?
-#define CPTCFG_IWLMVM 1
-
 #include <zircon/errors.h>
 #include <zircon/types.h>
 
diff --git a/third_party/iwlwifi/test/utils-test.cc b/third_party/iwlwifi/test/utils-test.cc
index c565903..6a2f84e 100644
--- a/third_party/iwlwifi/test/utils-test.cc
+++ b/third_party/iwlwifi/test/utils-test.cc
@@ -15,6 +15,8 @@
  */
 // Unittest code for the functions in mvm/utils.c
 
+#include <iterator>
+
 #include <zxtest/zxtest.h>
 
 extern "C" {
@@ -37,7 +39,7 @@
             ZX_ERR_OUT_OF_RANGE);
 
   // Invalid pointer
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(0, WLAN_INFO_BAND_5GHZ, nullptr),
+  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(0, WLAN_INFO_BAND_FIVE_GHZ, nullptr),
             ZX_ERR_INVALID_ARGS);
 
 #if 0   // NEEDS_PORTING
@@ -46,24 +48,27 @@
 #endif  // NEEDS_PORTING
 
   // 2.4 GHz: data rate: 1 Mbps ~ 54 Mbps
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(10 /* 1 Mbpsz */, WLAN_INFO_BAND_2GHZ, &idx),
+  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(10 /* 1 Mbpsz */, WLAN_INFO_BAND_TWO_GHZ, &idx),
             ZX_OK);
   EXPECT_EQ(idx, 0);
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(3 /* 54 Mbpsz */, WLAN_INFO_BAND_2GHZ, &idx),
+  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(3 /* 54 Mbpsz */, WLAN_INFO_BAND_TWO_GHZ, &idx),
             ZX_OK);
   EXPECT_EQ(idx, 11);
 
   // 5 GHz: data rate: 6 Mbps ~ 54 Mbps
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(13 /* 6 Mbps */, WLAN_INFO_BAND_5GHZ, &idx), ZX_OK);
+  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(13 /* 6 Mbps */, WLAN_INFO_BAND_FIVE_GHZ, &idx),
+            ZX_OK);
   EXPECT_EQ(idx, 0);
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(3 /* 54 Mbps */, WLAN_INFO_BAND_5GHZ, &idx), ZX_OK);
+  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(3 /* 54 Mbps */, WLAN_INFO_BAND_FIVE_GHZ, &idx),
+            ZX_OK);
   EXPECT_EQ(idx, 7);
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(10 /* 1 Mbps */, WLAN_INFO_BAND_5GHZ, &idx),
+  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(10 /* 1 Mbps */, WLAN_INFO_BAND_FIVE_GHZ, &idx),
             ZX_ERR_NOT_FOUND);
 
   // Not in the table
-  EXPECT_EQ(iwl_mvm_legacy_rate_to_mac80211_idx(0 /* random number */, WLAN_INFO_BAND_5GHZ, &idx),
-            ZX_ERR_NOT_FOUND);
+  EXPECT_EQ(
+      iwl_mvm_legacy_rate_to_mac80211_idx(0 /* random number */, WLAN_INFO_BAND_FIVE_GHZ, &idx),
+      ZX_ERR_NOT_FOUND);
 }
 
 TEST_F(UtilsTest, Dot11ToHWRate) {
@@ -75,18 +80,18 @@
   uint32_t data_rate;
 
   // 2.4 GHz, idx: 0 ~ 11
-  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_2GHZ, 0 /* 1 Mbps */, &data_rate));
+  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_TWO_GHZ, 0 /* 1 Mbps */, &data_rate));
   EXPECT_EQ(data_rate, TO_HALF_MBPS(1));
-  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_2GHZ, 11 /* 54 Mbps */, &data_rate));
+  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_TWO_GHZ, 11 /* 54 Mbps */, &data_rate));
   EXPECT_EQ(data_rate, TO_HALF_MBPS(54));
-  EXPECT_EQ(ZX_ERR_INVALID_ARGS, mac80211_idx_to_data_rate(WLAN_INFO_BAND_2GHZ, 12, &data_rate));
+  EXPECT_EQ(ZX_ERR_INVALID_ARGS, mac80211_idx_to_data_rate(WLAN_INFO_BAND_TWO_GHZ, 12, &data_rate));
 
   // 5 GHz, idx: 0 ~ 7
-  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_5GHZ, 0 /* 6 Mbps */, &data_rate));
+  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_FIVE_GHZ, 0 /* 6 Mbps */, &data_rate));
   EXPECT_EQ(data_rate, TO_HALF_MBPS(6));
-  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_5GHZ, 7 /* 54 Mbps */, &data_rate));
+  EXPECT_EQ(ZX_OK, mac80211_idx_to_data_rate(WLAN_INFO_BAND_FIVE_GHZ, 7 /* 54 Mbps */, &data_rate));
   EXPECT_EQ(data_rate, TO_HALF_MBPS(54));
-  EXPECT_EQ(ZX_ERR_INVALID_ARGS, mac80211_idx_to_data_rate(WLAN_INFO_BAND_5GHZ, 8, &data_rate));
+  EXPECT_EQ(ZX_ERR_INVALID_ARGS, mac80211_idx_to_data_rate(WLAN_INFO_BAND_FIVE_GHZ, 8, &data_rate));
 
   // For future 60 GHz
   EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, mac80211_idx_to_data_rate(WLAN_INFO_BAND_COUNT, 0, &data_rate));
@@ -121,7 +126,7 @@
           .associated = true,
       },
   };
-  uint8_t vif_count = ARRAY_SIZE(mvmvifs);
+  uint8_t vif_count = std::size(mvmvifs);
   ZX_ASSERT(vif_count <= MAX_NUM_MVMVIF);
 
   // Initialize the 'mvm' structure (and its 'mvmvif').
diff --git a/third_party/iwlwifi/test/wlan-pkt-builder.cc b/third_party/iwlwifi/test/wlan-pkt-builder.cc
index 3b471f2..4cae28d 100644
--- a/third_party/iwlwifi/test/wlan-pkt-builder.cc
+++ b/third_party/iwlwifi/test/wlan-pkt-builder.cc
@@ -29,8 +29,8 @@
   mac_pkt_->body_size = len - mac_pkt_->header_size;
 
   *wlan_pkt_ = {};
-  wlan_pkt_->packet_head.data_buffer = &*buf_;
-  wlan_pkt_->packet_head.data_size = len;
+  wlan_pkt_->mac_frame_buffer = &*buf_;
+  wlan_pkt_->mac_frame_size = len;
   wlan_pkt_->info.tx_flags = 0;
   wlan_pkt_->info.channel_bandwidth = CHANNEL_BANDWIDTH_CBW20;
 }
@@ -51,9 +51,11 @@
 
 WlanPktBuilder::~WlanPktBuilder() = default;
 
-std::shared_ptr<WlanPktBuilder::WlanPkt> WlanPktBuilder::build() {
-  static constexpr uint8_t kMacPkt[] = {
-      0x08, 0x01,                          // frame_ctrl
+std::shared_ptr<WlanPktBuilder::WlanPkt> WlanPktBuilder::build(uint16_t fc) {
+  const uint8_t fc0 = (uint8_t)fc & 0xff;
+  const uint8_t fc1 = (uint8_t)(fc >> 8);
+  const uint8_t kMacPkt[] = {
+      fc0,  fc1,                           // frame_ctrl
       0x00, 0x00,                          // duration
       0x11, 0x22, 0x33, 0x44, 0x55, 0x66,  // MAC1
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06,  // MAC2
diff --git a/third_party/iwlwifi/test/wlan-pkt-builder.h b/third_party/iwlwifi/test/wlan-pkt-builder.h
index ecc7ee7..d63610e 100644
--- a/third_party/iwlwifi/test/wlan-pkt-builder.h
+++ b/third_party/iwlwifi/test/wlan-pkt-builder.h
@@ -7,14 +7,14 @@
 #ifndef SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_WLAN_PKT_BUILDER_H_
 #define SRC_CONNECTIVITY_WLAN_DRIVERS_THIRD_PARTY_INTEL_IWLWIFI_TEST_WLAN_PKT_BUILDER_H_
 
-#include <fuchsia/hardware/wlan/mac/c/banjo.h>
+#include <fuchsia/hardware/wlan/softmac/c/banjo.h>
 #include <stdint.h>
 
 #include <memory>
 
 #include <zxtest/zxtest.h>
 
-#include "third_party/iwlwifi/platform/ieee80211.h"
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
 
 namespace wlan::testing {
 
@@ -64,7 +64,7 @@
   WlanPktBuilder& operator=(const WlanPktBuilder&) = delete;  // copy assignment
   WlanPktBuilder& operator=(WlanPktBuilder&&) = delete;       // move assignment
 
-  std::shared_ptr<WlanPkt> build();
+  std::shared_ptr<WlanPkt> build(uint16_t fc = 0x0801);
 };
 
 }  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/wlan-softmac-device-test.cc b/third_party/iwlwifi/test/wlan-softmac-device-test.cc
new file mode 100644
index 0000000..9fd15fa
--- /dev/null
+++ b/third_party/iwlwifi/test/wlan-softmac-device-test.cc
@@ -0,0 +1,900 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/iwlwifi/platform/wlan-softmac-device.h"
+
+#include <fidl/fuchsia.wlan.ieee80211/cpp/wire_types.h>
+#include <fuchsia/hardware/wlan/associnfo/cpp/banjo.h>
+#include <fuchsia/hardware/wlan/softmac/cpp/banjo.h>
+#include <fuchsia/wlan/ieee80211/c/fidl.h>
+#include <lib/mock-function/mock-function.h>
+#include <lib/zx/channel.h>
+#include <stdlib.h>
+#include <zircon/errors.h>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+#include <iterator>
+#include <list>
+#include <memory>
+#include <utility>
+
+#include <zxtest/zxtest.h>
+
+extern "C" {
+#include "third_party/iwlwifi/iwl-nvm-parse.h"
+#include "third_party/iwlwifi/mvm/mvm.h"
+}  // extern "C"
+
+#include "third_party/iwlwifi/platform/mvm-mlme.h"
+#include "third_party/iwlwifi/platform/scoped_utils.h"
+#include "third_party/iwlwifi/test/mock-trans.h"
+#include "third_party/iwlwifi/test/single-ap-test.h"
+#include "third_party/iwlwifi/test/wlan-pkt-builder.h"
+
+namespace wlan::testing {
+namespace {
+
+constexpr size_t kListenInterval = 100;
+constexpr uint8_t kInvalidBandIdFillByte = 0xa5;
+constexpr wlan_info_band_t kInvalidBandId = 0xa5a5a5a5;
+constexpr zx_handle_t kDummyMlmeChannel = 73939133;  // An arbitrary value not ZX_HANDLE_INVALID
+
+using recv_cb_t = mock_function::MockFunction<void, void*, const wlan_rx_packet_t*>;
+
+// Short-cut to access the iwl_cfg80211_rates[] structure and convert it to 802.11 rate.
+//
+// Args:
+//   index: the index of iwl_cfg80211_rates[].
+//
+// Returns:
+//   the 802.11 rate.
+//
+static unsigned expected_rate(size_t index) {
+  return cfg_rates_to_80211(iwl_cfg80211_rates[index]);
+}
+
+// The wrapper used by wlan_softmac_ifc_t.recv() to call mock-up.
+void recv_wrapper(void* cookie, const wlan_rx_packet_t* packet) {
+  auto recv = reinterpret_cast<recv_cb_t*>(cookie);
+  recv->Call(cookie, packet);
+}
+
+class WlanSoftmacDeviceTest : public SingleApTest {
+ public:
+  WlanSoftmacDeviceTest() {
+    mvmvif_.reset(reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif))));
+    mvmvif_->mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
+    mvmvif_->mlme_channel = kDummyMlmeChannel;
+    mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
+    mvmvif_->bss_conf = {.beacon_int = kListenInterval};
+
+    device_ = std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(nullptr, sim_trans_.iwl_trans(),
+                                                                   0, mvmvif_.get());
+  }
+  ~WlanSoftmacDeviceTest() override {}
+
+ protected:
+  wlan::iwlwifi::unique_free_ptr<struct iwl_mvm_vif> mvmvif_;
+  std::unique_ptr<::wlan::iwlwifi::WlanSoftmacDevice> device_;
+};
+
+//////////////////////////////////// Helper Functions  /////////////////////////////////////////////
+
+TEST_F(WlanSoftmacDeviceTest, ComposeBandList) {
+  struct iwl_nvm_data nvm_data;
+  wlan_info_band_t bands[WLAN_INFO_BAND_COUNT];
+
+  // nothing enabled
+  memset(&nvm_data, 0, sizeof(nvm_data));
+  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
+  EXPECT_EQ(0, compose_band_list(&nvm_data, bands));
+  EXPECT_EQ(kInvalidBandId, bands[0]);
+  EXPECT_EQ(kInvalidBandId, bands[1]);
+
+  // 2.4GHz only
+  memset(&nvm_data, 0, sizeof(nvm_data));
+  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
+  nvm_data.sku_cap_band_24ghz_enable = true;
+  EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
+  EXPECT_EQ(WLAN_INFO_BAND_TWO_GHZ, bands[0]);
+  EXPECT_EQ(kInvalidBandId, bands[1]);
+
+  // 5GHz only
+  memset(&nvm_data, 0, sizeof(nvm_data));
+  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
+  nvm_data.sku_cap_band_52ghz_enable = true;
+  EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
+  EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, bands[0]);
+  EXPECT_EQ(kInvalidBandId, bands[1]);
+
+  // both bands enabled
+  memset(&nvm_data, 0, sizeof(nvm_data));
+  memset(bands, kInvalidBandIdFillByte, sizeof(bands));
+  nvm_data.sku_cap_band_24ghz_enable = true;
+  nvm_data.sku_cap_band_52ghz_enable = true;
+  EXPECT_EQ(2, compose_band_list(&nvm_data, bands));
+  EXPECT_EQ(WLAN_INFO_BAND_TWO_GHZ, bands[0]);
+  EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, bands[1]);
+}
+
+TEST_F(WlanSoftmacDeviceTest, FillBandInfos) {
+  // The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
+
+  wlan_info_band_t bands[WLAN_INFO_BAND_COUNT] = {
+      WLAN_INFO_BAND_TWO_GHZ,
+      WLAN_INFO_BAND_FIVE_GHZ,
+  };
+  wlan_info_band_info_t band_infos[WLAN_INFO_BAND_COUNT] = {};
+
+  fill_band_infos(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, std::size(bands),
+                  band_infos);
+  // 2.4Ghz
+  wlan_info_band_info_t* exp_band_info = &band_infos[0];
+  EXPECT_EQ(WLAN_INFO_BAND_TWO_GHZ, exp_band_info->band);
+  EXPECT_EQ(true, exp_band_info->ht_supported);
+  EXPECT_EQ(expected_rate(0), exp_band_info->rates[0]);    // 1Mbps
+  EXPECT_EQ(expected_rate(11), exp_band_info->rates[11]);  // 54Mbps
+  EXPECT_EQ(2407, exp_band_info->supported_channels.base_freq);
+  EXPECT_EQ(1, exp_band_info->supported_channels.channels[0]);
+  EXPECT_EQ(13, exp_band_info->supported_channels.channels[12]);
+  // 5GHz
+  exp_band_info = &band_infos[1];
+  EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, exp_band_info->band);
+  EXPECT_EQ(true, exp_band_info->ht_supported);
+  EXPECT_EQ(expected_rate(4), exp_band_info->rates[0]);   // 6Mbps
+  EXPECT_EQ(expected_rate(11), exp_band_info->rates[7]);  // 54Mbps
+  EXPECT_EQ(5000, exp_band_info->supported_channels.base_freq);
+  EXPECT_EQ(36, exp_band_info->supported_channels.channels[0]);
+  EXPECT_EQ(165, exp_band_info->supported_channels.channels[24]);
+}
+
+TEST_F(WlanSoftmacDeviceTest, FillBandInfosOnly5GHz) {
+  // The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
+
+  wlan_info_band_t bands[WLAN_INFO_BAND_COUNT] = {
+      WLAN_INFO_BAND_FIVE_GHZ,
+      0,
+  };
+  wlan_info_band_info_t band_infos[WLAN_INFO_BAND_COUNT] = {};
+
+  fill_band_infos(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, 1, band_infos);
+  // 5GHz
+  wlan_info_band_info_t* exp_band_info = &band_infos[0];
+  EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, exp_band_info->band);
+  EXPECT_EQ(true, exp_band_info->ht_supported);
+  EXPECT_EQ(expected_rate(4), exp_band_info->rates[0]);   // 6Mbps
+  EXPECT_EQ(expected_rate(11), exp_band_info->rates[7]);  // 54Mbps
+  EXPECT_EQ(5000, exp_band_info->supported_channels.base_freq);
+  EXPECT_EQ(36, exp_band_info->supported_channels.channels[0]);
+  EXPECT_EQ(165, exp_band_info->supported_channels.channels[24]);
+  // index 1 should be empty.
+  exp_band_info = &band_infos[1];
+  EXPECT_EQ(false, exp_band_info->ht_supported);
+  EXPECT_EQ(0x00, exp_band_info->rates[0]);
+  EXPECT_EQ(0x00, exp_band_info->rates[7]);
+  EXPECT_EQ(0, exp_band_info->supported_channels.channels[0]);
+}
+
+TEST_F(WlanSoftmacDeviceTest, Query) {
+  // Test input null pointers
+  uint32_t options = 0;
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacQuery(options, nullptr));
+
+  wlan_softmac_info_t info = {};
+  ASSERT_EQ(ZX_OK, device_->WlanSoftmacQuery(options, &info));
+  EXPECT_EQ(WLAN_MAC_ROLE_CLIENT, info.mac_role);
+
+  //
+  // The below code assumes the test/sim-default-nvm.cc contains 2 bands.
+  //
+  //   .bands[0]: WLAN_INFO_BAND_TWO_GHZ
+  //   .bands[1]: WLAN_INFO_BAND_FIVE_GHZ
+  //
+  ASSERT_EQ(2, info.bands_count);
+  EXPECT_EQ(expected_rate(0), info.bands[0].rates[0]);    // 1 Mbps
+  EXPECT_EQ(expected_rate(7), info.bands[0].rates[7]);    // 18 Mbps
+  EXPECT_EQ(expected_rate(11), info.bands[0].rates[11]);  // 54 Mbps
+  EXPECT_EQ(expected_rate(4), info.bands[1].rates[0]);    // 6 Mbps
+  EXPECT_EQ(165, info.bands[1].supported_channels.channels[24]);
+}
+
+TEST_F(WlanSoftmacDeviceTest, MacStart) {
+  // Test input null pointers
+  wlan_softmac_ifc_protocol_ops_t proto_ops = {
+      .recv = recv_wrapper,
+  };
+  wlan_softmac_ifc_protocol_t ifc = {.ops = &proto_ops};
+  zx::channel mlme_channel;
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacStart(nullptr, &mlme_channel));
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacStart(&ifc, nullptr));
+
+  // Test callback function
+  recv_cb_t mock_recv;  // To mock up the wlan_softmac_ifc_t.recv().
+  mlme_channel = zx::channel(static_cast<zx_handle_t>(0xF000));
+  ASSERT_EQ(ZX_OK, device_->WlanSoftmacStart(&ifc, &mlme_channel));
+  // Expect the above line would copy the 'ifc'. Then set expectation below and fire test.
+  mock_recv.ExpectCall(&mock_recv, nullptr);
+  mvmvif_->ifc.ops->recv(&mock_recv, nullptr);
+  mock_recv.VerifyAndClear();
+}
+
+TEST_F(WlanSoftmacDeviceTest, MacStartSmeChannel) {
+  // The normal case. A channel will be transferred to MLME.
+  constexpr zx_handle_t kChannelOne = static_cast<zx_handle_t>(0xF001);
+  constexpr zx_handle_t kChannelTwo = static_cast<zx_handle_t>(0xF002);
+  mvmvif_->mlme_channel = kChannelOne;
+  wlan_softmac_ifc_protocol_ops_t proto_ops = {
+      .recv = recv_wrapper,
+  };
+  wlan_softmac_ifc_protocol_t ifc = {.ops = &proto_ops};
+  zx::channel mlme_channel(kChannelTwo);
+  ASSERT_EQ(ZX_OK, device_->WlanSoftmacStart(&ifc, &mlme_channel));
+  ASSERT_EQ(mlme_channel.get(), kChannelOne);           // The channel handle is returned.
+  ASSERT_EQ(mvmvif_->mlme_channel, ZX_HANDLE_INVALID);  // Driver no longer holds the ownership.
+
+  // Since the driver no longer owns the handle, the start should fail.
+  ASSERT_EQ(ZX_ERR_ALREADY_BOUND, device_->WlanSoftmacStart(&ifc, &mlme_channel));
+}
+
+TEST_F(WlanSoftmacDeviceTest, Release) {
+  // Create a channel. Let this test case holds one end while driver holds the other end.
+  char dummy[1];
+  zx_handle_t case_end;
+  ASSERT_EQ(zx_channel_create(0 /* option */, &case_end, &mvmvif_->mlme_channel), ZX_OK);
+  ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0), ZX_OK);
+
+  // Call release and the sme channel should be closed so that we will get a peer-close error while
+  // trying to write any data to it.
+  mvmvif_.release();
+  device_.release()->DdkRelease();
+  ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0),
+            ZX_ERR_PEER_CLOSED);
+}
+
+// The class for WLAN device MAC testing.
+//
+class MacInterfaceTest : public WlanSoftmacDeviceTest, public MockTrans {
+ public:
+  MacInterfaceTest() : ifc_{ .ops = &proto_ops_, } , proto_ops_{ .recv = recv_wrapper, } {
+    zx_handle_t wlanphy_impl_channel = mvmvif_->mlme_channel;
+    zx::channel mlme_channel;
+    ASSERT_EQ(ZX_OK, device_->WlanSoftmacStart(&ifc_, &mlme_channel));
+    ASSERT_EQ(wlanphy_impl_channel, mlme_channel);
+
+    // Add the interface to MVM instance.
+    mvmvif_->mvm->mvmvif[0] = mvmvif_.get();
+  }
+
+  ~MacInterfaceTest() {
+    VerifyExpectation();  // Ensure all expectations had been met.
+
+    // Restore the original callback for other test cases not using the mock.
+    ResetSendCmdFunc();
+
+    // Stop the MAC to free resources we allocated.
+    // This must be called after we verify the expected commands and restore the mock command
+    // callback so that the stop command doesn't mess up the test case expectation.
+    device_->WlanSoftmacStop();
+    VerifyStaHasBeenRemoved();
+  }
+
+  // Used in MockCommand constructor to indicate if the command needs to be either
+  //
+  //   - returned immediately (with a status code), or
+  //   - passed to the sim_mvm.c.
+  //
+  enum SimMvmBehavior {
+    kSimMvmReturnWithStatus,
+    kSimMvmBypassToSimMvm,
+  };
+
+  // A flexible mock-up of firmware command for testing code. Testing code can decide to either call
+  // the simulated firmware or return the status code immediately.
+  //
+  //   cmd_id: the command ID. Sometimes composed with WIDE_ID() macro.
+  //   behavior: determine what this mockup command is to do.
+  //   status: the status code to return when behavior is 'kSimMvmReturnWithStatus'.
+  //
+  class MockCommand {
+   public:
+    MockCommand(uint32_t cmd_id, SimMvmBehavior behavior, zx_status_t status)
+        : cmd_id_(cmd_id), behavior_(behavior), status_(status) {}
+    MockCommand(uint32_t cmd_id) : MockCommand(cmd_id, kSimMvmBypassToSimMvm, ZX_OK) {}
+
+    ~MockCommand() {}
+
+    uint32_t cmd_id_;
+    SimMvmBehavior behavior_;
+    zx_status_t status_;
+  };
+  typedef std::list<MockCommand> expected_cmd_id_list;
+  typedef zx_status_t (*fp_send_cmd)(struct iwl_trans* trans, struct iwl_host_cmd* cmd);
+
+  // Public for MockSendCmd().
+  expected_cmd_id_list expected_cmd_ids;
+  fp_send_cmd original_send_cmd;
+
+ protected:
+  zx_status_t SetChannel(const wlan_channel_t* channel) {
+    uint32_t option = 0;
+    return device_->WlanSoftmacSetChannel(option, channel);
+  }
+
+  zx_status_t ConfigureBss(const bss_config_t* config) {
+    uint32_t option = 0;
+    return device_->WlanSoftmacConfigureBss(option, config);
+  }
+
+  zx_status_t ConfigureAssoc(const wlan_assoc_ctx_t* config) {
+    uint32_t option = 0;
+    return device_->WlanSoftmacConfigureAssoc(option, config);
+  }
+
+  zx_status_t ClearAssoc() {
+    uint32_t option = 0;
+    uint8_t
+        peer_addr[::fuchsia_wlan_ieee80211::wire::kMacAddrLen];  // Not used since all info were
+                                                                 // saved in mvmvif_sta_ already.
+    return device_->WlanSoftmacClearAssoc(option, peer_addr);
+  }
+
+  zx_status_t SetKey(const wlan_key_config_t* key_config) {
+    uint32_t option = 0;
+    IWL_INFO(nullptr, "Calling set_key");
+    return device_->WlanSoftmacSetKey(option, key_config);
+  }
+  // The following functions are for mocking up the firmware commands.
+  //
+  // The mock function will return the special error ZX_ERR_INTERNAL when the expectation
+  // is not expected.
+
+  // Set the expected commands sending to the firmware.
+  //
+  // Args:
+  //   cmd_ids: list of expected commands. Will be matched in order.
+  //
+  void ExpectSendCmd(const expected_cmd_id_list& cmd_ids) {
+    expected_cmd_ids = cmd_ids;
+
+    // Re-define the 'dev' field in the 'struct iwl_trans' to a test instance of this class.
+    sim_trans_.iwl_trans()->dev = reinterpret_cast<struct device*>(this);
+
+    // Setup the mock function for send command.
+    original_send_cmd = sim_trans_.iwl_trans()->ops->send_cmd;
+    sim_trans_.iwl_trans()->ops->send_cmd = MockSendCmd;
+  }
+
+  // Reset the send command function to the original one, so that the test case would stop checking
+  // commands one by one.
+  void ResetSendCmdFunc() {
+    if (original_send_cmd) {
+      IWL_INFO(nullptr, "Reseting send_cmd.");
+      sim_trans_.iwl_trans()->ops->send_cmd = original_send_cmd;
+    } else {
+      IWL_WARN(nullptr, "No original send_cmd found.");
+    }
+  }
+
+  static zx_status_t MockSendCmd(struct iwl_trans* trans, struct iwl_host_cmd* cmd) {
+    MacInterfaceTest* this_ = reinterpret_cast<MacInterfaceTest*>(trans->dev);
+
+    // remove the first one and match.
+    expected_cmd_id_list& expected = this_->expected_cmd_ids;
+    ZX_ASSERT_MSG(!expected.empty(),
+                  "A command (0x%04x) is going to send, but no command is expected.\n", cmd->id);
+
+    // check the command ID.
+    auto exp = expected.front();
+    ZX_ASSERT_MSG(exp.cmd_id_ == cmd->id,
+                  "The command doesn't match! Expect: 0x%04x, actual: 0x%04x.\n", exp.cmd_id_,
+                  cmd->id);
+    expected.pop_front();
+
+    if (exp.behavior_ == kSimMvmBypassToSimMvm) {
+      return this_->original_send_cmd(trans, cmd);
+    } else {
+      return exp.status_;
+    }
+  }
+
+  void VerifyExpectation() {
+    for (expected_cmd_id_list::iterator it = expected_cmd_ids.begin(); it != expected_cmd_ids.end();
+         it++) {
+      printf("  ==> 0x%04x\n", it->cmd_id_);
+    }
+    ASSERT_TRUE(expected_cmd_ids.empty(), "The expected command set is not empty.");
+
+    mock_tx_.VerifyAndClear();
+  }
+
+  void VerifyStaHasBeenRemoved() {
+    auto mvm = mvmvif_->mvm;
+
+    for (size_t i = 0; i < std::size(mvm->fw_id_to_mac_id); i++) {
+      struct iwl_mvm_sta* mvm_sta = mvm->fw_id_to_mac_id[i];
+      ASSERT_EQ(nullptr, mvm_sta);
+    }
+    ASSERT_EQ(0, mvm->vif_count);
+  }
+
+  // Mock function for Tx.
+  mock_function::MockFunction<zx_status_t,  // return value
+                              size_t,       // packet size
+                              uint16_t,     // cmd + group_id
+                              int           // txq_id
+                              >
+      mock_tx_;
+
+  static zx_status_t tx_wrapper(struct iwl_trans* trans, struct ieee80211_mac_packet* pkt,
+                                const struct iwl_device_cmd* dev_cmd, int txq_id) {
+    auto test = GET_TEST(MacInterfaceTest, trans);
+    return test->mock_tx_.Call(pkt->header_size + pkt->headroom_used_size + pkt->body_size,
+                               WIDE_ID(dev_cmd->hdr.group_id, dev_cmd->hdr.cmd), txq_id);
+  }
+
+  wlan_softmac_ifc_protocol_t ifc_;
+  wlan_softmac_ifc_protocol_ops_t proto_ops_;
+  static constexpr bss_config_t kBssConfig = {
+      .bssid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
+      .bss_type = BSS_TYPE_INFRASTRUCTURE,
+      .remote = true,
+  };
+  // Assoc context without HT related data.
+  static constexpr wlan_assoc_ctx_t kAssocCtx = {
+      .listen_interval = kListenInterval,
+  };
+
+  // Assoc context with HT related data. (The values below comes from real data in manual test)
+  static constexpr wlan_assoc_ctx_t kHtAssocCtx = {
+      .listen_interval = kListenInterval,
+      .channel =
+          {
+              .primary = 157,
+              .cbw = CHANNEL_BANDWIDTH_CBW80,
+          },
+      .rates_cnt = 8,
+      .rates =
+          {
+              140,
+              18,
+              152,
+              36,
+              176,
+              72,
+              96,
+              108,
+          },
+      .has_ht_cap = true,
+      .ht_cap =
+          {
+              .supported_mcs_set =
+                  {
+                      .bytes =
+                          {
+                              255,
+                              255,
+                              0,
+                              0,
+                              0,
+                              0,
+                              0,
+                              0,
+                              0,
+                              0,
+                              0,
+                              0,
+                              1,
+                              0,
+                              0,
+                              0,
+                          },
+                  },
+          },
+  };
+};
+
+// Test the set_channel().
+//
+TEST_F(MacInterfaceTest, TestSetChannel) {
+  ExpectSendCmd(expected_cmd_id_list({
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for add_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for change_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
+  }));
+
+  mvmvif_->csa_bcn_pending = true;  // Expect to be clear because this is client role.
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  EXPECT_EQ(false, mvmvif_->csa_bcn_pending);
+}
+
+// Test call set_channel() multiple times.
+//
+TEST_F(MacInterfaceTest, TestMultipleSetChannel) {
+  ExpectSendCmd(expected_cmd_id_list({
+      // for the first SetChannel()
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for add_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for change_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
+
+      // for the second SetChannel()
+      MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),  // for remove_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for add_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for change_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
+  }));
+
+  for (size_t i = 0; i < 2; i++) {
+    ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  }
+}
+
+// Test the unsupported MAC role.
+//
+TEST_F(MacInterfaceTest, TestSetChannelWithUnsupportedRole) {
+  ExpectSendCmd(expected_cmd_id_list({
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for add_chanctx
+      MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),  // for change_chanctx
+  }));
+
+  mvmvif_->mac_role = WLAN_MAC_ROLE_AP;
+  ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, SetChannel(&kChannel));
+}
+
+// Tests calling SetChannel()/ConfigureBss() again without ConfigureAssoc()/ClearAssoc()
+TEST_F(MacInterfaceTest, DuplicateSetChannel) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
+  struct iwl_mvm_phy_ctxt* phy_ctxt = mvmvif_->phy_ctxt;
+  ASSERT_NE(nullptr, phy_ctxt);
+  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
+  // Call SetChannel() again. This should return the same phy context but ConfigureBss()
+  // should setup a new STA.
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  struct iwl_mvm_phy_ctxt* new_phy_ctxt = mvmvif_->phy_ctxt;
+  ASSERT_NE(nullptr, new_phy_ctxt);
+  ASSERT_EQ(phy_ctxt, new_phy_ctxt);
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  struct iwl_mvm_sta* new_mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
+  // Now Associate and disassociate - this should release and reset the phy ctxt.
+  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
+  ASSERT_EQ(IWL_STA_AUTHORIZED, new_mvm_sta->sta_state);
+  ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
+  ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
+
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+  ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
+  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
+}
+
+// Test ConfigureBss()
+//
+TEST_F(MacInterfaceTest, TestConfigureBss) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+
+  ExpectSendCmd(expected_cmd_id_list({
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
+      MockCommand(WIDE_ID(LONG_GROUP, SCD_QUEUE_CFG)),
+      MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
+  }));
+
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  // Ensure the BSSID was copied into mvmvif
+  ASSERT_EQ(memcmp(mvmvif_->bss_conf.bssid, kBssConfig.bssid, ETH_ALEN), 0);
+  ASSERT_EQ(memcmp(mvmvif_->bssid, kBssConfig.bssid, ETH_ALEN), 0);
+}
+
+// Test duplicate BSS config.
+//
+TEST_F(MacInterfaceTest, DuplicateConfigureBss) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  ASSERT_EQ(ZX_ERR_ALREADY_BOUND, ConfigureBss(&kBssConfig));
+}
+
+// Test unsupported bss_type.
+//
+TEST_F(MacInterfaceTest, UnsupportedBssType) {
+  static constexpr bss_config_t kUnsupportedBssConfig = {
+      .bssid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
+      .bss_type = BSS_TYPE_INDEPENDENT,
+      .remote = true,
+  };
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kUnsupportedBssConfig));
+}
+
+// Test failed ADD_STA command.
+//
+TEST_F(MacInterfaceTest, TestFailedAddSta) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+
+  ExpectSendCmd(expected_cmd_id_list({
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD), kSimMvmReturnWithStatus,
+                  ZX_ERR_BUFFER_TOO_SMALL /* an arbitrary error */),
+  }));
+
+  ASSERT_EQ(ZX_ERR_BUFFER_TOO_SMALL, ConfigureBss(&kBssConfig));
+}
+
+// Test exception handling in driver.
+//
+TEST_F(MacInterfaceTest, TestExceptionHandling) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+
+  // Test the beacon interval checking.
+  mvmvif_->bss_conf.beacon_int = 0;
+  EXPECT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kBssConfig));
+  mvmvif_->bss_conf.beacon_int = 16;  // which just passes the check.
+
+  // Test the phy_ctxt checking.
+  auto backup_phy_ctxt = mvmvif_->phy_ctxt;
+  mvmvif_->phy_ctxt = nullptr;
+  EXPECT_EQ(ZX_ERR_BAD_STATE, ConfigureBss(&kBssConfig));
+  mvmvif_->phy_ctxt = backup_phy_ctxt;
+
+  // Test the case we run out of slots for STA.
+  std::list<std::unique_ptr<::wlan::iwlwifi::WlanSoftmacDevice>> devices;
+  for (size_t i = 0; i < IWL_MVM_STATION_COUNT; i++) {
+    // Pretend the STA is not assigned so that we can add it again.
+    devices.emplace_back(std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(
+        nullptr, sim_trans_.iwl_trans(), 0, mvmvif_.get()));
+    std::swap(device_, devices.back());
+    ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  }
+
+  // However, the last one should fail because we run out of all slots in fw_id_to_mac_id[].
+  devices.emplace_back(std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(
+      nullptr, sim_trans_.iwl_trans(), 0, mvmvif_.get()));
+  std::swap(device_, devices.back());
+  ASSERT_EQ(ZX_ERR_NO_RESOURCES, ConfigureBss(&kBssConfig));
+}
+
+// The test is used to test the typical procedure to connect to an open network.
+//
+TEST_F(MacInterfaceTest, AssociateToOpenNetwork) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
+  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
+  struct iwl_mvm* mvm = mvmvif_->mvm;
+  ASSERT_GT(list_length(&mvm->time_event_list), 0);
+
+  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
+  ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
+  ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
+  ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
+
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+  ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
+  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
+  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
+}
+
+// Back to back calls of ClearAssoc().
+TEST_F(MacInterfaceTest, ClearAssocAfterClearAssoc) {
+  ASSERT_NE(ZX_OK, ClearAssoc());
+  ASSERT_NE(ZX_OK, ClearAssoc());
+}
+
+// ClearAssoc() should cleanup when called without Assoc
+TEST_F(MacInterfaceTest, ClearAssocAfterNoAssoc) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
+  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
+  struct iwl_mvm* mvm = mvmvif_->mvm;
+  ASSERT_GT(list_length(&mvm->time_event_list), 0);
+
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+  ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
+  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
+  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
+
+  // Call ClearAssoc() again to check if it is handled correctly.
+  ASSERT_NE(ZX_OK, ClearAssoc());
+}
+
+// ClearAssoc() should cleanup when called after a failed Assoc
+TEST_F(MacInterfaceTest, ClearAssocAfterFailedAssoc) {
+  SetChannel(&kChannel);
+  ConfigureBss(&kBssConfig);
+
+  struct iwl_mvm* mvm = mvmvif_->mvm;
+  ASSERT_GT(list_length(&mvm->time_event_list), 0);
+  // Fail the association by forcing some relevant internal state.
+  auto orig = mvmvif_->uploaded;
+  mvmvif_->uploaded = false;
+  ASSERT_EQ(ZX_ERR_IO, ConfigureAssoc(&kAssocCtx));
+  mvmvif_->uploaded = orig;
+
+  // ClearAssoc will clean up the failed association.
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+  ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
+  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
+  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
+
+  // Call ClearAssoc() again to check if it is handled correctly.
+  ASSERT_NE(ZX_OK, ClearAssoc());
+}
+
+// This test case is to verify ConfigureAssoc() with HT wlan_assoc_ctx_t input can successfully
+// trigger LQ_CMD with correct data.
+TEST_F(MacInterfaceTest, AssocWithHtConfig) {
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+
+  ExpectSendCmd(expected_cmd_id_list({
+      MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
+      MockCommand(WIDE_ID(LONG_GROUP, LQ_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
+      MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
+      MockCommand(WIDE_ID(LONG_GROUP, MCAST_FILTER_CMD)),
+  }));
+
+  // Extract LQ_CMD data.
+  struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
+  struct iwl_lq_cmd* lq_cmd = &mvm_sta->lq_sta.rs_drv.lq;
+
+  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
+  struct iwl_mvm* mvm = mvmvif_->mvm;
+  ASSERT_GT(list_length(&mvm->time_event_list), 0);
+
+  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kHtAssocCtx));
+
+  // Verify the values in LQ_CMD API structure.
+  EXPECT_EQ(lq_cmd->sta_id, 0);
+  EXPECT_EQ(lq_cmd->reduced_tpc, 0);
+  EXPECT_EQ(lq_cmd->flags, 0);
+  EXPECT_EQ(lq_cmd->mimo_delim, 0);
+  EXPECT_EQ(lq_cmd->single_stream_ant_msk, 1);
+  EXPECT_EQ(lq_cmd->dual_stream_ant_msk, 3);
+  EXPECT_EQ(lq_cmd->initial_rate_index[0], 0);
+  EXPECT_EQ(lq_cmd->initial_rate_index[1], 0);
+  EXPECT_EQ(lq_cmd->initial_rate_index[2], 0);
+  EXPECT_EQ(lq_cmd->initial_rate_index[3], 0);
+  EXPECT_EQ(lq_cmd->agg_time_limit, 0x0fa0);
+  EXPECT_EQ(lq_cmd->agg_disable_start_th, 3);
+  EXPECT_EQ(lq_cmd->agg_frame_cnt_limit, 1);
+  EXPECT_EQ(lq_cmd->reserved2, 0);
+
+  // Verify rate_n_flags in the table.
+  EXPECT_EQ(lq_cmd->rs_table[0], 0x4103);
+  // The value of RS_MNG_RETRY_TABLE_INITIAL_RATE_NUM is 3.
+  EXPECT_EQ(lq_cmd->rs_table[3], 0x4102);
+
+  EXPECT_EQ(lq_cmd->ss_params, 0);
+
+  // Stop checking following commands one by one.
+  ResetSendCmdFunc();
+
+  // Clean up the association states.
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+}
+
+// Check to ensure keys are set during assoc and deleted after disassoc
+// for now use open network
+TEST_F(MacInterfaceTest, SetKeysTest) {
+  constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
+  ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
+  struct iwl_mvm* mvm = mvmvif_->mvm;
+  ASSERT_GT(list_length(&mvm->time_event_list), 0);
+
+  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
+  ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
+  ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
+  ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
+
+  char keybuf[sizeof(wlan_key_config_t) + 16] = {};
+  wlan_key_config_t* key_config = new (keybuf) wlan_key_config_t();
+
+  // Set an arbitrary pairwise key.
+  key_config->cipher_type = 4;
+  key_config->key_type = 1;
+  key_config->key_idx = 0;
+  key_config->key_len = 16;
+  memcpy(key_config->cipher_oui, kIeeeOui, 3);
+  ASSERT_EQ(ZX_OK, SetKey(key_config));
+  // Expect bit 0 to be set.
+  ASSERT_EQ(*mvm->fw_key_table, 0x1);
+
+  // Set an arbitrary group key.
+  key_config->key_type = 2;
+  key_config->key_idx = 1;
+  ASSERT_EQ(ZX_OK, SetKey(key_config));
+  // Expect bit 1 to be set as well.
+  ASSERT_EQ(*mvm->fw_key_table, 0x3);
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+  ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
+  ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
+  ASSERT_EQ(list_length(&mvm->time_event_list), 0);
+  // Both the keys should have been deleted.
+  ASSERT_EQ(*mvm->fw_key_table, 0x0);
+}
+
+// Check that we can sucessfully set some key configurations required for supported functionality.
+TEST_F(MacInterfaceTest, SetKeysSupportConfigs) {
+  constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
+  ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
+  ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
+  ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
+  ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
+
+  char keybuf[sizeof(wlan_key_config_t) + 16] = {};
+  wlan_key_config_t* key_config = new (keybuf) wlan_key_config_t();
+  key_config->key_len = 16;
+  memcpy(key_config->cipher_oui, kIeeeOui, 3);
+
+  // Default cipher configuration for WPA2/3 PTK.  This is data frame protection, required for
+  // WPA2/3.
+  key_config->cipher_type = fuchsia_wlan_ieee80211_CipherSuiteType_CCMP_128;
+  key_config->key_type = WLAN_KEY_TYPE_PAIRWISE;
+  key_config->key_idx = 0;
+  ASSERT_EQ(ZX_OK, SetKey(key_config));
+
+  // Default cipher configuration for WPA2/3 IGTK.  This is management frame protection, optional
+  // for WPA2 and required for WPA3.
+  key_config->cipher_type = fuchsia_wlan_ieee80211_CipherSuiteType_BIP_CMAC_128;
+  key_config->key_type = WLAN_KEY_TYPE_IGTK;
+  key_config->key_idx = 1;
+  ASSERT_EQ(ZX_OK, SetKey(key_config));
+
+  ASSERT_EQ(ZX_OK, ClearAssoc());
+}
+
+TEST_F(MacInterfaceTest, TxPktTooLong) {
+  SetChannel(&kChannel);
+  ConfigureBss(&kBssConfig);
+  BIND_TEST(sim_trans_.iwl_trans());
+
+  bindTx(tx_wrapper);
+  WlanPktBuilder builder;
+  std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
+  wlan_pkt->wlan_pkt()->mac_frame_size = WLAN_MSDU_MAX_LEN + 1;
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacQueueTx(0, wlan_pkt->wlan_pkt()));
+  unbindTx();
+}
+
+TEST_F(MacInterfaceTest, TxPktNotSupportedRole) {
+  SetChannel(&kChannel);
+  ConfigureBss(&kBssConfig);
+  BIND_TEST(sim_trans_.iwl_trans());
+
+  // Set to an unsupported role.
+  mvmvif_->mac_role = WLAN_MAC_ROLE_AP;
+
+  bindTx(tx_wrapper);
+  WlanPktBuilder builder;
+  std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacQueueTx(0, wlan_pkt->wlan_pkt()));
+  unbindTx();
+}
+
+// To test if a packet can be sent out.
+TEST_F(MacInterfaceTest, TxPkt) {
+  SetChannel(&kChannel);
+  ConfigureBss(&kBssConfig);
+  BIND_TEST(sim_trans_.iwl_trans());
+
+  bindTx(tx_wrapper);
+  WlanPktBuilder builder;
+  std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
+  mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len(), WIDE_ID(0, TX_CMD), IWL_MVM_DQA_MIN_MGMT_QUEUE);
+  ASSERT_EQ(ZX_OK, device_->WlanSoftmacQueueTx(0, wlan_pkt->wlan_pkt()));
+  unbindTx();
+}
+
+}  // namespace
+}  // namespace wlan::testing
diff --git a/third_party/iwlwifi/test/wlanphy-impl-device-test.cc b/third_party/iwlwifi/test/wlanphy-impl-device-test.cc
new file mode 100644
index 0000000..2f79271
--- /dev/null
+++ b/third_party/iwlwifi/test/wlanphy-impl-device-test.cc
@@ -0,0 +1,213 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// To test PHY device callback functions.
+
+#include <fuchsia/wlan/common/cpp/banjo.h>
+#include <fuchsia/wlan/internal/cpp/banjo.h>
+#include <zircon/listnode.h>
+#include <zircon/syscalls.h>
+
+#include <iterator>
+
+#include <zxtest/zxtest.h>
+
+extern "C" {
+#include "third_party/iwlwifi/mvm/mvm.h"
+}
+
+#include "third_party/iwlwifi/platform/ieee80211_include.h"
+#include "third_party/iwlwifi/platform/mvm-mlme.h"
+#include "third_party/iwlwifi/platform/wlanphy-impl-device.h"
+#include "third_party/iwlwifi/test/single-ap-test.h"
+
+namespace wlan::testing {
+namespace {
+
+constexpr size_t kListenInterval = 100;
+constexpr zx_handle_t kDummyMlmeChannel = 73939133;  // An arbitrary value not ZX_HANDLE_INVALID
+
+class WlanphyImplDeviceTest : public SingleApTest {
+ public:
+  WlanphyImplDeviceTest()
+      : mvmvif_sta_{
+            .mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans()),
+            .mac_role = WLAN_MAC_ROLE_CLIENT,
+            .bss_conf =
+                {
+                    .beacon_int = kListenInterval,
+                },
+        } {
+    device_ = sim_trans_.sim_device();
+  }
+  ~WlanphyImplDeviceTest() {}
+
+ protected:
+  struct iwl_mvm_vif mvmvif_sta_;  // The mvm_vif settings for station role.
+  wlan::iwlwifi::WlanphyImplDevice* device_;
+};
+
+/////////////////////////////////////       PHY       //////////////////////////////////////////////
+
+TEST_F(WlanphyImplDeviceTest, PhyGetSupportedMacRolesNullPtr) {
+  // Test input null pointers
+  ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanphyImplGetSupportedMacRoles(nullptr, 0));
+}
+
+TEST_F(WlanphyImplDeviceTest, PhyGetSupportedMacRoles) {
+  wlan_mac_role_t supported_mac_roles_list[fuchsia_wlan_common_MAX_SUPPORTED_MAC_ROLES] = {};
+  uint8_t supported_mac_roles_count = 0;
+
+  // Normal case
+  ASSERT_EQ(ZX_OK, device_->WlanphyImplGetSupportedMacRoles(supported_mac_roles_list,
+                                                            &supported_mac_roles_count));
+  EXPECT_EQ(supported_mac_roles_count, 1);
+  EXPECT_EQ(supported_mac_roles_list[0], WLAN_MAC_ROLE_CLIENT);
+}
+
+TEST_F(WlanphyImplDeviceTest, PhyPartialCreateCleanup) {
+  wlanphy_impl_create_iface_req_t req = {
+      .role = WLAN_MAC_ROLE_CLIENT,
+      .mlme_channel = kDummyMlmeChannel,
+  };
+  uint16_t iface_id;
+  struct iwl_trans* iwl_trans = sim_trans_.iwl_trans();
+
+  // Test input null pointers
+  ASSERT_OK(phy_create_iface(iwl_trans, &req, &iface_id));
+
+  // Ensure mvmvif got created and indexed.
+  struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
+  ASSERT_NOT_NULL(mvm->mvmvif[iface_id]);
+
+  // Ensure partial create failure removes it from the index.
+  phy_create_iface_undo(iwl_trans, iface_id);
+  ASSERT_NULL(mvm->mvmvif[iface_id]);
+}
+
+TEST_F(WlanphyImplDeviceTest, PhyCreateDestroySingleInterface) {
+  wlanphy_impl_create_iface_req_t req = {
+      .role = WLAN_MAC_ROLE_CLIENT,
+      .mlme_channel = kDummyMlmeChannel,
+  };
+  uint16_t iface_id;
+
+  // Test input null pointers
+  ASSERT_EQ(device_->WlanphyImplCreateIface(nullptr, &iface_id), ZX_ERR_INVALID_ARGS);
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, nullptr), ZX_ERR_INVALID_ARGS);
+  ASSERT_EQ(device_->WlanphyImplCreateIface(nullptr, nullptr), ZX_ERR_INVALID_ARGS);
+
+  // Test invalid inputs
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(MAX_NUM_MVMVIF), ZX_ERR_INVALID_ARGS);
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_ERR_NOT_FOUND);  // hasn't been added yet.
+
+  // To verify the internal state of MVM driver.
+  struct iwl_mvm* mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
+
+  // Add interface
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
+  ASSERT_EQ(iface_id, 0);  // the first interface should have id 0.
+  struct iwl_mvm_vif* mvmvif = mvm->mvmvif[iface_id];
+  ASSERT_NE(mvmvif, nullptr);
+  ASSERT_EQ(mvmvif->mac_role, WLAN_MAC_ROLE_CLIENT);
+  // Count includes phy device in addition to the newly created mac device.
+  ASSERT_EQ(fake_parent_->descendant_count(), 2);
+  device_->parent()->GetLatestChild()->InitOp();
+
+  // Remove interface
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_OK);
+  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
+  ASSERT_EQ(mvm->mvmvif[iface_id], nullptr);
+  ASSERT_EQ(fake_parent_->descendant_count(), 1);
+}
+
+TEST_F(WlanphyImplDeviceTest, PhyCreateDestroyMultipleInterfaces) {
+  wlanphy_impl_create_iface_req_t req = {
+      .role = WLAN_MAC_ROLE_CLIENT,
+      .mlme_channel = kDummyMlmeChannel,
+  };
+  uint16_t iface_id;
+  struct iwl_trans* iwl_trans = sim_trans_.iwl_trans();
+  struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);  // To verify the internal state of MVM driver
+
+  // Add 1st interface
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
+  ASSERT_EQ(iface_id, 0);
+  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_MAC_ROLE_CLIENT);
+  ASSERT_EQ(fake_parent_->descendant_count(), 2);
+  device_->parent()->GetLatestChild()->InitOp();
+
+  // Add 2nd interface
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
+  ASSERT_EQ(iface_id, 1);
+  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
+  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_MAC_ROLE_CLIENT);
+  ASSERT_EQ(fake_parent_->descendant_count(), 3);
+  device_->parent()->GetLatestChild()->InitOp();
+
+  // Add 3rd interface
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
+  ASSERT_EQ(iface_id, 2);
+  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
+  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_MAC_ROLE_CLIENT);
+  ASSERT_EQ(fake_parent_->descendant_count(), 4);
+  device_->parent()->GetLatestChild()->InitOp();
+
+  // Remove the 2nd interface
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(1), ZX_OK);
+  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
+  ASSERT_EQ(mvm->mvmvif[1], nullptr);
+  ASSERT_EQ(fake_parent_->descendant_count(), 3);
+
+  // Add a new interface and it should be the 2nd one.
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
+  ASSERT_EQ(iface_id, 1);
+  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
+  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_MAC_ROLE_CLIENT);
+  ASSERT_EQ(fake_parent_->descendant_count(), 4);
+  device_->parent()->GetLatestChild()->InitOp();
+
+  // Add 4th interface
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
+  ASSERT_EQ(iface_id, 3);
+  ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
+  ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_MAC_ROLE_CLIENT);
+  ASSERT_EQ(fake_parent_->descendant_count(), 5);
+  device_->parent()->GetLatestChild()->InitOp();
+
+  // Add 5th interface and it should fail
+  ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_ERR_NO_RESOURCES);
+  ASSERT_EQ(fake_parent_->descendant_count(), 5);
+
+  // Remove the 2nd interface
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(1), ZX_OK);
+  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
+  ASSERT_EQ(mvm->mvmvif[1], nullptr);
+  ASSERT_EQ(fake_parent_->descendant_count(), 4);
+
+  // Remove the 3rd interface
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(2), ZX_OK);
+  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
+  ASSERT_EQ(mvm->mvmvif[2], nullptr);
+  ASSERT_EQ(fake_parent_->descendant_count(), 3);
+
+  // Remove the 4th interface
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(3), ZX_OK);
+  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
+  ASSERT_EQ(mvm->mvmvif[3], nullptr);
+  ASSERT_EQ(fake_parent_->descendant_count(), 2);
+
+  // Remove the 1st interface
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_OK);
+  mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
+  ASSERT_EQ(mvm->mvmvif[0], nullptr);
+  ASSERT_EQ(fake_parent_->descendant_count(), 1);
+
+  // Remove the 1st interface again and it should fail.
+  ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_ERR_NOT_FOUND);
+  ASSERT_EQ(fake_parent_->descendant_count(), 1);
+}
+
+}  // namespace
+}  // namespace wlan::testing