diff mbox

[v2,4/8] wil6210: add support for adding and removing virtual interfaces

Message ID 1519668738-16912-5-git-send-email-merez@codeaurora.org (mailing list archive)
State Accepted
Commit 4aebd3bdbd8a26ebcd2398289e2379472d17825f
Delegated to: Kalle Valo
Headers show

Commit Message

Maya Erez Feb. 26, 2018, 6:12 p.m. UTC
From: Lior David <liord@codeaurora.org>

Add generic support in cfg80211 operations add_virtual_intf
and del_virtual_intf for adding/removing VIFs of any
interface type, and fix change_virtual_intf to allow changing
the interface type of a VIF. Previously these operations
only worked for the P2P_DEVICE interface which is not a real
VIF(it is management-only and shares radio with the main
interface).
Currently the interface combination is validated, the VIF is
added/removed in the firmware and the appropriate net/wireless
device is also added/removed.
Added minimal support for proper interface up/down and module
unload but most operations still work only on the main interface.

Signed-off-by: Lior David <liord@codeaurora.org>
Signed-off-by: Maya Erez <merez@codeaurora.org>
---
 drivers/net/wireless/ath/wil6210/cfg80211.c | 209 ++++++++++++++++++++++++----
 drivers/net/wireless/ath/wil6210/main.c     |  34 +++++
 drivers/net/wireless/ath/wil6210/netdev.c   | 163 +++++++++++++++++++---
 drivers/net/wireless/ath/wil6210/pcie_bus.c |  39 +++++-
 drivers/net/wireless/ath/wil6210/wil6210.h  |  21 ++-
 drivers/net/wireless/ath/wil6210/wmi.c      | 111 ++++++++++++++-
 6 files changed, 518 insertions(+), 59 deletions(-)
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c
index 2fd4af8..ce20ee4 100644
--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
+++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
@@ -18,6 +18,7 @@ 
 #include <linux/etherdevice.h>
 #include <linux/moduleparam.h>
 #include <net/netlink.h>
+#include <net/cfg80211.h>
 #include "wil6210.h"
 #include "wmi.h"
 #include "fw.h"
@@ -418,6 +419,53 @@  static void wil_cfg80211_stop_p2p_device(struct wiphy *wiphy,
 	mutex_unlock(&wil->mutex);
 }
 
+static int wil_cfg80211_validate_add_iface(struct wil6210_priv *wil,
+					   enum nl80211_iftype new_type)
+{
+	int i;
+	struct wireless_dev *wdev;
+	struct iface_combination_params params = {
+		.num_different_channels = 1,
+	};
+
+	for (i = 0; i < wil->max_vifs; i++) {
+		if (wil->vifs[i]) {
+			wdev = vif_to_wdev(wil->vifs[i]);
+			params.iftype_num[wdev->iftype]++;
+		}
+	}
+	params.iftype_num[new_type]++;
+	return cfg80211_check_combinations(wil->wiphy, &params);
+}
+
+static int wil_cfg80211_validate_change_iface(struct wil6210_priv *wil,
+					      struct wil6210_vif *vif,
+					      enum nl80211_iftype new_type)
+{
+	int i, ret = 0;
+	struct wireless_dev *wdev;
+	struct iface_combination_params params = {
+		.num_different_channels = 1,
+	};
+	bool check_combos = false;
+
+	for (i = 0; i < wil->max_vifs; i++) {
+		struct wil6210_vif *vif_pos = wil->vifs[i];
+
+		if (vif_pos && vif != vif_pos) {
+			wdev = vif_to_wdev(vif_pos);
+			params.iftype_num[wdev->iftype]++;
+			check_combos = true;
+		}
+	}
+
+	if (check_combos) {
+		params.iftype_num[new_type]++;
+		ret = cfg80211_check_combinations(wil->wiphy, &params);
+	}
+	return ret;
+}
+
 static struct wireless_dev *
 wil_cfg80211_add_iface(struct wiphy *wiphy, const char *name,
 		       unsigned char name_assign_type,
@@ -425,53 +473,136 @@  static void wil_cfg80211_stop_p2p_device(struct wiphy *wiphy,
 		       struct vif_params *params)
 {
 	struct wil6210_priv *wil = wiphy_to_wil(wiphy);
-	struct net_device *ndev = wil->main_ndev;
+	struct net_device *ndev_main = wil->main_ndev, *ndev;
 	struct wil6210_vif *vif;
-	struct wireless_dev *p2p_wdev;
+	struct wireless_dev *p2p_wdev, *wdev;
+	int rc;
 
-	wil_dbg_misc(wil, "add_iface\n");
+	wil_dbg_misc(wil, "add_iface, type %d\n", type);
 
-	if (type != NL80211_IFTYPE_P2P_DEVICE) {
-		wil_err(wil, "unsupported iftype %d\n", type);
-		return ERR_PTR(-EINVAL);
+	/* P2P device is not a real virtual interface, it is a management-only
+	 * interface that shares the main interface.
+	 * Skip concurrency checks here.
+	 */
+	if (type == NL80211_IFTYPE_P2P_DEVICE) {
+		if (wil->p2p_wdev) {
+			wil_err(wil, "P2P_DEVICE interface already created\n");
+			return ERR_PTR(-EINVAL);
+		}
+
+		vif = kzalloc(sizeof(*vif), GFP_KERNEL);
+		if (!vif)
+			return ERR_PTR(-ENOMEM);
+
+		p2p_wdev = vif_to_wdev(vif);
+		p2p_wdev->iftype = type;
+		p2p_wdev->wiphy = wiphy;
+		/* use our primary ethernet address */
+		ether_addr_copy(p2p_wdev->address, ndev_main->perm_addr);
+
+		wil->p2p_wdev = p2p_wdev;
+
+		return p2p_wdev;
 	}
 
-	if (wil->p2p_wdev) {
-		wil_err(wil, "P2P_DEVICE interface already created\n");
+	if (!wil->wiphy->n_iface_combinations) {
+		wil_err(wil, "virtual interfaces not supported\n");
 		return ERR_PTR(-EINVAL);
 	}
 
-	vif = kzalloc(sizeof(*vif), GFP_KERNEL);
-	if (!vif)
-		return ERR_PTR(-ENOMEM);
+	rc = wil_cfg80211_validate_add_iface(wil, type);
+	if (rc) {
+		wil_err(wil, "iface validation failed, err=%d\n", rc);
+		return ERR_PTR(rc);
+	}
 
-	p2p_wdev = &vif->wdev;
-	p2p_wdev->iftype = type;
-	p2p_wdev->wiphy = wiphy;
-	/* use our primary ethernet address */
-	ether_addr_copy(p2p_wdev->address, ndev->perm_addr);
+	vif = wil_vif_alloc(wil, name, name_assign_type, type);
+	if (IS_ERR(vif))
+		return ERR_CAST(vif);
 
-	wil->p2p_wdev = p2p_wdev;
+	ndev = vif_to_ndev(vif);
+	ether_addr_copy(ndev->perm_addr, ndev_main->perm_addr);
+	if (is_valid_ether_addr(params->macaddr)) {
+		ether_addr_copy(ndev->dev_addr, params->macaddr);
+	} else {
+		ether_addr_copy(ndev->dev_addr, ndev_main->perm_addr);
+		ndev->dev_addr[0] = (ndev->dev_addr[0] ^ (1 << vif->mid)) |
+			0x2; /* locally administered */
+	}
+	wdev = vif_to_wdev(vif);
+	ether_addr_copy(wdev->address, ndev->dev_addr);
+
+	rc = wil_vif_add(wil, vif);
+	if (rc)
+		goto out;
 
-	return p2p_wdev;
+	wil_info(wil, "added VIF, mid %d iftype %d MAC %pM\n",
+		 vif->mid, type, wdev->address);
+	return wdev;
+out:
+	wil_vif_free(vif);
+	return ERR_PTR(rc);
+}
+
+int wil_vif_prepare_stop(struct wil6210_priv *wil, struct wil6210_vif *vif)
+{
+	struct wireless_dev *wdev = vif_to_wdev(vif);
+	struct net_device *ndev;
+	int rc;
+
+	if (wdev->iftype != NL80211_IFTYPE_AP)
+		return 0;
+
+	ndev = vif_to_ndev(vif);
+	if (netif_carrier_ok(ndev)) {
+		rc = wmi_pcp_stop(vif);
+		if (rc) {
+			wil_info(wil, "failed to stop AP, status %d\n",
+				 rc);
+			/* continue */
+		}
+		netif_carrier_off(ndev);
+	}
+
+	return 0;
 }
 
 static int wil_cfg80211_del_iface(struct wiphy *wiphy,
 				  struct wireless_dev *wdev)
 {
 	struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+	struct wil6210_vif *vif = wdev_to_vif(wil, wdev);
+	int rc;
 
 	wil_dbg_misc(wil, "del_iface\n");
 
-	if (wdev != wil->p2p_wdev) {
-		wil_err(wil, "delete of incorrect interface 0x%p\n", wdev);
+	if (wdev->iftype == NL80211_IFTYPE_P2P_DEVICE) {
+		if (wdev != wil->p2p_wdev) {
+			wil_err(wil, "delete of incorrect interface 0x%p\n",
+				wdev);
+			return -EINVAL;
+		}
+
+		wil_cfg80211_stop_p2p_device(wiphy, wdev);
+		wil_p2p_wdev_free(wil);
+		return 0;
+	}
+
+	if (vif->mid == 0) {
+		wil_err(wil, "cannot remove the main interface\n");
 		return -EINVAL;
 	}
 
-	wil_cfg80211_stop_p2p_device(wiphy, wdev);
-	wil_p2p_wdev_free(wil);
+	rc = wil_vif_prepare_stop(wil, vif);
+	if (rc)
+		goto out;
 
-	return 0;
+	wil_info(wil, "deleted VIF, mid %d iftype %d MAC %pM\n",
+		 vif->mid, wdev->iftype, wdev->address);
+
+	wil_vif_remove(wil, vif->mid);
+out:
+	return rc;
 }
 
 static int wil_cfg80211_change_iface(struct wiphy *wiphy,
@@ -486,7 +617,19 @@  static int wil_cfg80211_change_iface(struct wiphy *wiphy,
 
 	wil_dbg_misc(wil, "change_iface: type=%d\n", type);
 
-	if (netif_running(ndev) && !wil_is_recovery_blocked(wil)) {
+	if (wiphy->n_iface_combinations) {
+		rc = wil_cfg80211_validate_change_iface(wil, vif, type);
+		if (rc) {
+			wil_err(wil, "iface validation failed, err=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* do not reset FW when there are active VIFs,
+	 * because it can cause significant disruption
+	 */
+	if (!wil_has_other_up_ifaces(wil, ndev) &&
+	    netif_running(ndev) && !wil_is_recovery_blocked(wil)) {
 		wil_dbg_misc(wil, "interface is up. resetting...\n");
 		mutex_lock(&wil->mutex);
 		__wil_down(wil);
@@ -511,8 +654,17 @@  static int wil_cfg80211_change_iface(struct wiphy *wiphy,
 		return -EOPNOTSUPP;
 	}
 
-	wdev->iftype = type;
+	if (vif->mid != 0 && wil_has_up_ifaces(wil)) {
+		wil_vif_prepare_stop(wil, vif);
+		rc = wmi_port_delete(wil, vif->mid);
+		if (rc)
+			return rc;
+		rc = wmi_port_allocate(wil, vif->mid, ndev->dev_addr, type);
+		if (rc)
+			return rc;
+	}
 
+	wdev->iftype = type;
 	return 0;
 }
 
@@ -2007,6 +2159,13 @@  int wil_cfg80211_iface_combinations_from_fw(
 		combo = (struct wil_fw_concurrency_combo *)limit;
 	}
 
+	wil_dbg_misc(wil, "multiple VIFs supported, n_mids %d\n", conc->n_mids);
+	wil->max_vifs = conc->n_mids + 1; /* including main interface */
+	if (wil->max_vifs > WIL_MAX_VIFS) {
+		wil_info(wil, "limited number of VIFs supported(%d, FW %d)\n",
+			 WIL_MAX_VIFS, wil->max_vifs);
+		wil->max_vifs = WIL_MAX_VIFS;
+	}
 	wiphy->n_iface_combinations = n_combos;
 	wiphy->iface_combinations = iface_combinations;
 	return 0;
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
index b151f1c..2926bd1 100644
--- a/drivers/net/wireless/ath/wil6210/main.c
+++ b/drivers/net/wireless/ath/wil6210/main.c
@@ -565,6 +565,7 @@  int wil_priv_init(struct wil6210_priv *wil)
 	wil->vring_idle_trsh = 16;
 
 	wil->reply_mid = U8_MAX;
+	wil->max_vifs = 1;
 
 	return 0;
 
@@ -1115,6 +1116,33 @@  static void wil_pre_fw_config(struct wil6210_priv *wil)
 	}
 }
 
+static int wil_restore_vifs(struct wil6210_priv *wil)
+{
+	struct wil6210_vif *vif;
+	struct net_device *ndev;
+	struct wireless_dev *wdev;
+	int i, rc;
+
+	for (i = 0; i < wil->max_vifs; i++) {
+		vif = wil->vifs[i];
+		if (!vif)
+			continue;
+		if (vif->mid) {
+			ndev = vif_to_ndev(vif);
+			wdev = vif_to_wdev(vif);
+			rc = wmi_port_allocate(wil, vif->mid, ndev->dev_addr,
+					       wdev->iftype);
+			if (rc) {
+				wil_err(wil, "fail to restore VIF %d type %d, rc %d\n",
+					i, wdev->iftype, rc);
+				return rc;
+			}
+		}
+	}
+
+	return 0;
+}
+
 /*
  * We reset all the structures, and we reset the UMAC.
  * After calling this routine, you're expected to reload
@@ -1277,6 +1305,12 @@  int wil_reset(struct wil6210_priv *wil, bool load_fw)
 			return rc;
 		}
 
+		rc = wil_restore_vifs(wil);
+		if (rc) {
+			wil_err(wil, "failed to restore vifs, rc %d\n", rc);
+			return rc;
+		}
+
 		wil_collect_fw_info(wil);
 
 		if (wil->ps_profile != WMI_PS_PROFILE_TYPE_DEFAULT)
diff --git a/drivers/net/wireless/ath/wil6210/netdev.c b/drivers/net/wireless/ath/wil6210/netdev.c
index 95570b8..e23a80c 100644
--- a/drivers/net/wireless/ath/wil6210/netdev.c
+++ b/drivers/net/wireless/ath/wil6210/netdev.c
@@ -16,13 +16,38 @@ 
  */
 
 #include <linux/etherdevice.h>
+#include <linux/rtnetlink.h>
 #include "wil6210.h"
 #include "txrx.h"
 
+bool wil_has_other_up_ifaces(struct wil6210_priv *wil,
+			     struct net_device *ndev)
+{
+	int i;
+	struct wil6210_vif *vif;
+	struct net_device *ndev_i;
+
+	for (i = 0; i < wil->max_vifs; i++) {
+		vif = wil->vifs[i];
+		if (vif) {
+			ndev_i = vif_to_ndev(vif);
+			if (ndev_i != ndev && ndev_i->flags & IFF_UP)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+bool wil_has_up_ifaces(struct wil6210_priv *wil)
+{
+	return wil_has_other_up_ifaces(wil, NULL);
+}
+
 static int wil_open(struct net_device *ndev)
 {
 	struct wil6210_priv *wil = ndev_to_wil(ndev);
-	int rc;
+	int rc = 0;
 
 	wil_dbg_misc(wil, "open\n");
 
@@ -32,13 +57,16 @@  static int wil_open(struct net_device *ndev)
 		return -EINVAL;
 	}
 
-	rc = wil_pm_runtime_get(wil);
-	if (rc < 0)
-		return rc;
+	if (!wil_has_other_up_ifaces(wil, ndev)) {
+		wil_dbg_misc(wil, "open, first iface\n");
+		rc = wil_pm_runtime_get(wil);
+		if (rc < 0)
+			return rc;
 
-	rc = wil_up(wil);
-	if (rc)
-		wil_pm_runtime_put(wil);
+		rc = wil_up(wil);
+		if (rc)
+			wil_pm_runtime_put(wil);
+	}
 
 	return rc;
 }
@@ -46,13 +74,16 @@  static int wil_open(struct net_device *ndev)
 static int wil_stop(struct net_device *ndev)
 {
 	struct wil6210_priv *wil = ndev_to_wil(ndev);
-	int rc;
+	int rc = 0;
 
 	wil_dbg_misc(wil, "stop\n");
 
-	rc = wil_down(wil);
-	if (!rc)
-		wil_pm_runtime_put(wil);
+	if (!wil_has_other_up_ifaces(wil, ndev)) {
+		wil_dbg_misc(wil, "stop, last iface\n");
+		rc = wil_down(wil);
+		if (!rc)
+			wil_pm_runtime_put(wil);
+	}
 
 	return rc;
 }
@@ -201,14 +232,32 @@  static void wil_vif_init(struct wil6210_vif *vif)
 	INIT_LIST_HEAD(&vif->probe_client_pending);
 }
 
+static u8 wil_vif_find_free_mid(struct wil6210_priv *wil)
+{
+	u8 i;
+
+	for (i = 0; i < wil->max_vifs; i++) {
+		if (!wil->vifs[i])
+			return i;
+	}
+
+	return U8_MAX;
+}
+
 struct wil6210_vif *
 wil_vif_alloc(struct wil6210_priv *wil, const char *name,
-	      unsigned char name_assign_type, enum nl80211_iftype iftype,
-	      u8 mid)
+	      unsigned char name_assign_type, enum nl80211_iftype iftype)
 {
 	struct net_device *ndev;
 	struct wireless_dev *wdev;
 	struct wil6210_vif *vif;
+	u8 mid;
+
+	mid = wil_vif_find_free_mid(wil);
+	if (mid == U8_MAX) {
+		wil_err(wil, "no available virtual interface\n");
+		return ERR_PTR(-EINVAL);
+	}
 
 	ndev = alloc_netdev(sizeof(*vif), name, name_assign_type,
 			    wil_dev_setup);
@@ -216,8 +265,13 @@  struct wil6210_vif *
 		dev_err(wil_to_dev(wil), "alloc_netdev failed\n");
 		return ERR_PTR(-ENOMEM);
 	}
-	if (mid == 0)
+	if (mid == 0) {
 		wil->main_ndev = ndev;
+	} else {
+		ndev->priv_destructor = wil_ndev_destructor;
+		ndev->needs_free_netdev = true;
+	}
+
 	vif = ndev_to_vif(ndev);
 	vif->ndev = ndev;
 	vif->wil = wil;
@@ -263,7 +317,7 @@  void *wil_if_alloc(struct device *dev)
 	wil_dbg_misc(wil, "if_alloc\n");
 
 	vif = wil_vif_alloc(wil, "wlan%d", NET_NAME_UNKNOWN,
-			    NL80211_IFTYPE_STATION, 0);
+			    NL80211_IFTYPE_STATION);
 	if (IS_ERR(vif)) {
 		dev_err(dev, "wil_vif_alloc failed\n");
 		rc = -ENOMEM;
@@ -301,10 +355,43 @@  void wil_if_free(struct wil6210_priv *wil)
 	wil_cfg80211_deinit(wil);
 }
 
+int wil_vif_add(struct wil6210_priv *wil, struct wil6210_vif *vif)
+{
+	struct net_device *ndev = vif_to_ndev(vif);
+	struct wireless_dev *wdev = vif_to_wdev(vif);
+	bool any_active = wil_has_up_ifaces(wil);
+	int rc;
+
+	ASSERT_RTNL();
+
+	if (wil->vifs[vif->mid]) {
+		dev_err(&ndev->dev, "VIF with mid %d already in use\n",
+			vif->mid);
+		return -EEXIST;
+	}
+	if (any_active && vif->mid != 0) {
+		rc = wmi_port_allocate(wil, vif->mid, ndev->dev_addr,
+				       wdev->iftype);
+		if (rc)
+			return rc;
+	}
+	rc = register_netdevice(ndev);
+	if (rc < 0) {
+		dev_err(&ndev->dev, "Failed to register netdev: %d\n", rc);
+		if (any_active && vif->mid != 0)
+			wmi_port_delete(wil, vif->mid);
+		return rc;
+	}
+
+	wil->vifs[vif->mid] = vif;
+	return 0;
+}
+
 int wil_if_add(struct wil6210_priv *wil)
 {
 	struct wiphy *wiphy = wil->wiphy;
 	struct net_device *ndev = wil->main_ndev;
+	struct wil6210_vif *vif = ndev_to_vif(ndev);
 	int rc;
 
 	wil_dbg_misc(wil, "entered");
@@ -324,11 +411,11 @@  int wil_if_add(struct wil6210_priv *wil)
 
 	wil_update_net_queues_bh(wil, NULL, true);
 
-	rc = register_netdev(ndev);
-	if (rc < 0) {
-		dev_err(&ndev->dev, "Failed to register netdev: %d\n", rc);
+	rtnl_lock();
+	rc = wil_vif_add(wil, vif);
+	rtnl_unlock();
+	if (rc < 0)
 		goto out_wiphy;
-	}
 
 	return 0;
 
@@ -337,6 +424,40 @@  int wil_if_add(struct wil6210_priv *wil)
 	return rc;
 }
 
+void wil_vif_remove(struct wil6210_priv *wil, u8 mid)
+{
+	struct wil6210_vif *vif;
+	struct net_device *ndev;
+	bool any_active = wil_has_up_ifaces(wil);
+
+	ASSERT_RTNL();
+	if (mid >= wil->max_vifs) {
+		wil_err(wil, "invalid MID: %d\n", mid);
+		return;
+	}
+
+	vif = wil->vifs[mid];
+	if (!vif) {
+		wil_err(wil, "MID %d not registered\n", mid);
+		return;
+	}
+
+	ndev = vif_to_ndev(vif);
+	/* during unregister_netdevice cfg80211_leave may perform operations
+	 * such as stop AP, disconnect, so we only clear the VIF afterwards
+	 */
+	unregister_netdevice(ndev);
+
+	if (any_active && vif->mid != 0)
+		wmi_port_delete(wil, vif->mid);
+
+	wil->vifs[mid] = NULL;
+	/* for VIFs, ndev will be freed by destructor after RTNL is unlocked.
+	 * the main interface will be freed in wil_if_free, we need to keep it
+	 * a bit longer so logging macros will work.
+	 */
+}
+
 void wil_if_remove(struct wil6210_priv *wil)
 {
 	struct net_device *ndev = wil->main_ndev;
@@ -344,6 +465,8 @@  void wil_if_remove(struct wil6210_priv *wil)
 
 	wil_dbg_misc(wil, "if_remove\n");
 
-	unregister_netdev(ndev);
+	rtnl_lock();
+	wil_vif_remove(wil, 0);
+	rtnl_unlock();
 	wiphy_unregister(wdev->wiphy);
 }
diff --git a/drivers/net/wireless/ath/wil6210/pcie_bus.c b/drivers/net/wireless/ath/wil6210/pcie_bus.c
index a61ffd9..1fd7614 100644
--- a/drivers/net/wireless/ath/wil6210/pcie_bus.c
+++ b/drivers/net/wireless/ath/wil6210/pcie_bus.c
@@ -137,6 +137,20 @@  void wil_enable_irq(struct wil6210_priv *wil)
 	enable_irq(wil->pdev->irq);
 }
 
+static void wil_remove_all_additional_vifs(struct wil6210_priv *wil)
+{
+	struct wil6210_vif *vif;
+	int i;
+
+	for (i = 1; i < wil->max_vifs; i++) {
+		vif = wil->vifs[i];
+		if (vif) {
+			wil_vif_prepare_stop(wil, vif);
+			wil_vif_remove(wil, vif->mid);
+		}
+	}
+}
+
 /* Bus ops */
 static int wil_if_pcie_enable(struct wil6210_priv *wil)
 {
@@ -148,10 +162,8 @@  static int wil_if_pcie_enable(struct wil6210_priv *wil)
 	 */
 	int msi_only = pdev->msi_enabled;
 	bool _use_msi = use_msi;
-	bool wmi_only = test_bit(WMI_FW_CAPABILITY_WMI_ONLY,
-				 wil->fw_capabilities);
 
-	wil_dbg_misc(wil, "if_pcie_enable, wmi_only %d\n", wmi_only);
+	wil_dbg_misc(wil, "if_pcie_enable\n");
 
 	pci_set_master(pdev);
 
@@ -172,11 +184,9 @@  static int wil_if_pcie_enable(struct wil6210_priv *wil)
 	if (rc)
 		goto stop_master;
 
-	/* need reset here to obtain MAC or in case of WMI-only FW, full reset
-	 * and fw loading takes place
-	 */
+	/* need reset here to obtain MAC */
 	mutex_lock(&wil->mutex);
-	rc = wil_reset(wil, wmi_only);
+	rc = wil_reset(wil, false);
 	mutex_unlock(&wil->mutex);
 	if (rc)
 		goto release_irq;
@@ -356,6 +366,18 @@  static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		goto bus_disable;
 	}
 
+	/* in case of WMI-only FW, perform full reset and FW loading */
+	if (test_bit(WMI_FW_CAPABILITY_WMI_ONLY, wil->fw_capabilities)) {
+		wil_dbg_misc(wil, "Loading WMI only FW\n");
+		mutex_lock(&wil->mutex);
+		rc = wil_reset(wil, true);
+		mutex_unlock(&wil->mutex);
+		if (rc) {
+			wil_err(wil, "failed to load WMI only FW\n");
+			goto if_remove;
+		}
+	}
+
 	if (IS_ENABLED(CONFIG_PM))
 		wil->pm_notify.notifier_call = wil6210_pm_notify;
 
@@ -372,6 +394,8 @@  static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
 	return 0;
 
+if_remove:
+	wil_if_remove(wil);
 bus_disable:
 	wil_if_pcie_disable(wil);
 err_iounmap:
@@ -402,6 +426,7 @@  static void wil_pcie_remove(struct pci_dev *pdev)
 	wil6210_debugfs_remove(wil);
 	rtnl_lock();
 	wil_p2p_wdev_free(wil);
+	wil_remove_all_additional_vifs(wil);
 	rtnl_unlock();
 	wil_if_remove(wil);
 	wil_if_pcie_disable(wil);
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h
index fa8df41..8ab4b5a 100644
--- a/drivers/net/wireless/ath/wil6210/wil6210.h
+++ b/drivers/net/wireless/ath/wil6210/wil6210.h
@@ -50,6 +50,11 @@ 
 #define WIL_DEFAULT_BUS_REQUEST_KBPS 128000 /* ~1Gbps */
 #define WIL_MAX_BUS_REQUEST_KBPS 800000 /* ~6.1Gbps */
 
+/* maximum number of virtual interfaces the driver supports
+ * (including the main interface)
+ */
+#define WIL_MAX_VIFS 4
+
 /**
  * extract bits [@b0:@b1] (inclusive) from the value @x
  * it should be @b0 <= @b1, or result is incorrect
@@ -714,11 +719,12 @@  struct wil6210_priv {
 	DECLARE_BITMAP(hw_capa, hw_capa_last);
 	DECLARE_BITMAP(fw_capabilities, WMI_FW_CAPABILITY_MAX);
 	DECLARE_BITMAP(platform_capa, WIL_PLATFORM_CAPA_MAX);
-	u8 n_mids; /* number of additional MIDs as reported by FW */
 	u32 recovery_count; /* num of FW recovery attempts in a short time */
 	u32 recovery_state; /* FW recovery state machine */
 	unsigned long last_fw_recovery; /* jiffies of last fw recovery */
 	wait_queue_head_t wq; /* for all wait_event() use */
+	u8 max_vifs; /* maximum number of interfaces, including main */
+	struct wil6210_vif *vifs[WIL_MAX_VIFS];
 	/* profile */
 	struct cfg80211_chan_def monitor_chandef;
 	u32 monitor_flags;
@@ -935,11 +941,16 @@  void wil_memcpy_toio_32(volatile void __iomem *dst, const void *src,
 
 struct wil6210_vif *
 wil_vif_alloc(struct wil6210_priv *wil, const char *name,
-	      unsigned char name_assign_type, enum nl80211_iftype iftype,
-	      u8 mid);
+	      unsigned char name_assign_type, enum nl80211_iftype iftype);
+void wil_vif_free(struct wil6210_vif *vif);
 void *wil_if_alloc(struct device *dev);
+bool wil_has_other_up_ifaces(struct wil6210_priv *wil,
+			     struct net_device *ndev);
+bool wil_has_up_ifaces(struct wil6210_priv *wil);
 void wil_if_free(struct wil6210_priv *wil);
+int wil_vif_add(struct wil6210_priv *wil, struct wil6210_vif *vif);
 int wil_if_add(struct wil6210_priv *wil);
+void wil_vif_remove(struct wil6210_priv *wil, u8 mid);
 void wil_if_remove(struct wil6210_priv *wil);
 int wil_priv_init(struct wil6210_priv *wil);
 void wil_priv_deinit(struct wil6210_priv *wil);
@@ -998,6 +1009,9 @@  int wmi_ps_dev_profile_cfg(struct wil6210_priv *wil,
 int wmi_set_mgmt_retry(struct wil6210_priv *wil, u8 retry_short);
 int wmi_get_mgmt_retry(struct wil6210_priv *wil, u8 *retry_short);
 int wmi_new_sta(struct wil6210_vif *vif, const u8 *mac, u8 aid);
+int wmi_port_allocate(struct wil6210_priv *wil, u8 mid,
+		      const u8 *mac, enum nl80211_iftype iftype);
+int wmi_port_delete(struct wil6210_priv *wil, u8 mid);
 int wil_addba_rx_request(struct wil6210_priv *wil, u8 mid,
 			 u8 cidxtid, u8 dialog_token, __le16 ba_param_set,
 			 __le16 ba_timeout, __le16 ba_seq_ctrl);
@@ -1039,6 +1053,7 @@  int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
 int wil_cfg80211_iface_combinations_from_fw(
 	struct wil6210_priv *wil,
 	const struct wil_fw_record_concurrency *conc);
+int wil_vif_prepare_stop(struct wil6210_priv *wil, struct wil6210_vif *vif);
 
 #if defined(CONFIG_WIL6210_DEBUGFS)
 int wil6210_debugfs_init(struct wil6210_priv *wil);
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index 044d698..23c28bf 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -656,11 +656,14 @@  static void wmi_evt_ready(struct wil6210_vif *vif, int id, void *d, int len)
 	struct wiphy *wiphy = wil_to_wiphy(wil);
 	struct wmi_ready_event *evt = d;
 
-	wil->n_mids = evt->numof_additional_mids;
-
 	wil_info(wil, "FW ver. %s(SW %d); MAC %pM; %d MID's\n",
 		 wil->fw_version, le32_to_cpu(evt->sw_version),
-		 evt->mac, wil->n_mids);
+		 evt->mac, evt->numof_additional_mids);
+	if (evt->numof_additional_mids + 1 < wil->max_vifs) {
+		wil_err(wil, "FW does not support enough MIDs (need %d)",
+			wil->max_vifs - 1);
+		return; /* FW load will fail after timeout */
+	}
 	/* ignore MAC address, we already have it from the boot loader */
 	strlcpy(wiphy->fw_version, wil->fw_version, sizeof(wiphy->fw_version));
 
@@ -2372,6 +2375,92 @@  int wmi_resume(struct wil6210_priv *wil)
 	return reply.evt.status;
 }
 
+int wmi_port_allocate(struct wil6210_priv *wil, u8 mid,
+		      const u8 *mac, enum nl80211_iftype iftype)
+{
+	int rc;
+	struct wmi_port_allocate_cmd cmd = {
+		.mid = mid,
+	};
+	struct {
+		struct wmi_cmd_hdr wmi;
+		struct wmi_port_allocated_event evt;
+	} __packed reply;
+
+	wil_dbg_misc(wil, "port allocate, mid %d iftype %d, mac %pM\n",
+		     mid, iftype, mac);
+
+	ether_addr_copy(cmd.mac, mac);
+	switch (iftype) {
+	case NL80211_IFTYPE_STATION:
+		cmd.port_role = WMI_PORT_STA;
+		break;
+	case NL80211_IFTYPE_AP:
+		cmd.port_role = WMI_PORT_AP;
+		break;
+	case NL80211_IFTYPE_P2P_CLIENT:
+		cmd.port_role = WMI_PORT_P2P_CLIENT;
+		break;
+	case NL80211_IFTYPE_P2P_GO:
+		cmd.port_role = WMI_PORT_P2P_GO;
+		break;
+	/* what about monitor??? */
+	default:
+		wil_err(wil, "unsupported iftype: %d\n", iftype);
+		return -EINVAL;
+	}
+
+	reply.evt.status = WMI_FW_STATUS_FAILURE;
+
+	rc = wmi_call(wil, WMI_PORT_ALLOCATE_CMDID, mid,
+		      &cmd, sizeof(cmd),
+		      WMI_PORT_ALLOCATED_EVENTID, &reply,
+		      sizeof(reply), 300);
+	if (rc) {
+		wil_err(wil, "failed to allocate port, status %d\n", rc);
+		return rc;
+	}
+	if (reply.evt.status != WMI_FW_STATUS_SUCCESS) {
+		wil_err(wil, "WMI_PORT_ALLOCATE returned status %d\n",
+			reply.evt.status);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int wmi_port_delete(struct wil6210_priv *wil, u8 mid)
+{
+	int rc;
+	struct wmi_port_delete_cmd cmd = {
+		.mid = mid,
+	};
+	struct {
+		struct wmi_cmd_hdr wmi;
+		struct wmi_port_deleted_event evt;
+	} __packed reply;
+
+	wil_dbg_misc(wil, "port delete, mid %d\n", mid);
+
+	reply.evt.status = WMI_FW_STATUS_FAILURE;
+
+	rc = wmi_call(wil, WMI_PORT_DELETE_CMDID, mid,
+		      &cmd, sizeof(cmd),
+		      WMI_PORT_DELETED_EVENTID, &reply,
+		      sizeof(reply), 2000);
+	if (rc) {
+		wil_err(wil, "failed to delete port, status %d\n", rc);
+		return rc;
+	}
+	if (reply.evt.status != WMI_FW_STATUS_SUCCESS) {
+		wil_err(wil, "WMI_PORT_DELETE returned status %d\n",
+			reply.evt.status);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static bool wmi_evt_call_handler(struct wil6210_vif *vif, int id,
 				 void *d, int len)
 {
@@ -2391,7 +2480,7 @@  static void wmi_event_handle(struct wil6210_priv *wil,
 			     struct wil6210_mbox_hdr *hdr)
 {
 	u16 len = le16_to_cpu(hdr->len);
-	struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev);
+	struct wil6210_vif *vif;
 
 	if ((hdr->type == WIL_MBOX_HDR_TYPE_WMI) &&
 	    (len >= sizeof(struct wmi_cmd_hdr))) {
@@ -2404,6 +2493,20 @@  static void wmi_event_handle(struct wil6210_priv *wil,
 			    eventid2name(id), id, wil->reply_id,
 			    wil->reply_mid);
 
+		if (mid == MID_BROADCAST)
+			mid = 0;
+		if (mid >= wil->max_vifs) {
+			wil_dbg_wmi(wil, "invalid mid %d, event skipped\n",
+				    mid);
+			return;
+		}
+		vif = wil->vifs[mid];
+		if (!vif) {
+			wil_dbg_wmi(wil, "event for empty VIF(%d), skipped\n",
+				    mid);
+			return;
+		}
+
 		/* check if someone waits for this event */
 		if (wil->reply_id && wil->reply_id == id &&
 		    wil->reply_mid == mid) {