Commit 34d4bc4d authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville

mac80211: support runtime interface type changes

Add support to mac80211 for changing the interface
type even when the interface is UP, if the driver
supports it.

To achieve this
 * add a new driver callback for switching,
 * split some of the interface up/down code out
   into new functions (do_open/do_stop), and
 * maintain an own __SDATA_RUNNING bit that will
   not be set during interface type, so that any
   other code doesn't use the interface.
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 87490f6d
...@@ -1537,6 +1537,12 @@ enum ieee80211_ampdu_mlme_action { ...@@ -1537,6 +1537,12 @@ enum ieee80211_ampdu_mlme_action {
* negative error code (which will be seen in userspace.) * negative error code (which will be seen in userspace.)
* Must be implemented and can sleep. * Must be implemented and can sleep.
* *
* @change_interface: Called when a netdevice changes type. This callback
* is optional, but only if it is supported can interface types be
* switched while the interface is UP. The callback may sleep.
* Note that while an interface is being switched, it will not be
* found by the interface iteration callbacks.
*
* @remove_interface: Notifies a driver that an interface is going down. * @remove_interface: Notifies a driver that an interface is going down.
* The @stop callback is called after this if it is the last interface * The @stop callback is called after this if it is the last interface
* and no monitor interfaces are present. * and no monitor interfaces are present.
...@@ -1693,6 +1699,9 @@ struct ieee80211_ops { ...@@ -1693,6 +1699,9 @@ struct ieee80211_ops {
void (*stop)(struct ieee80211_hw *hw); void (*stop)(struct ieee80211_hw *hw);
int (*add_interface)(struct ieee80211_hw *hw, int (*add_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif); struct ieee80211_vif *vif);
int (*change_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
enum nl80211_iftype new_type);
void (*remove_interface)(struct ieee80211_hw *hw, void (*remove_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif); struct ieee80211_vif *vif);
int (*config)(struct ieee80211_hw *hw, u32 changed); int (*config)(struct ieee80211_hw *hw, u32 changed);
......
...@@ -52,9 +52,6 @@ static int ieee80211_change_iface(struct wiphy *wiphy, ...@@ -52,9 +52,6 @@ static int ieee80211_change_iface(struct wiphy *wiphy,
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
int ret; int ret;
if (ieee80211_sdata_running(sdata))
return -EBUSY;
ret = ieee80211_if_change_type(sdata, type); ret = ieee80211_if_change_type(sdata, type);
if (ret) if (ret)
return ret; return ret;
......
...@@ -54,6 +54,20 @@ static inline int drv_add_interface(struct ieee80211_local *local, ...@@ -54,6 +54,20 @@ static inline int drv_add_interface(struct ieee80211_local *local,
return ret; return ret;
} }
static inline int drv_change_interface(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
enum nl80211_iftype type)
{
int ret;
might_sleep();
trace_drv_change_interface(local, sdata, type);
ret = local->ops->change_interface(&local->hw, &sdata->vif, type);
trace_drv_return_int(local, ret);
return ret;
}
static inline void drv_remove_interface(struct ieee80211_local *local, static inline void drv_remove_interface(struct ieee80211_local *local,
struct ieee80211_vif *vif) struct ieee80211_vif *vif)
{ {
......
...@@ -136,6 +136,31 @@ TRACE_EVENT(drv_add_interface, ...@@ -136,6 +136,31 @@ TRACE_EVENT(drv_add_interface,
) )
); );
TRACE_EVENT(drv_change_interface,
TP_PROTO(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata,
enum nl80211_iftype type),
TP_ARGS(local, sdata, type),
TP_STRUCT__entry(
LOCAL_ENTRY
VIF_ENTRY
__field(u32, new_type)
),
TP_fast_assign(
LOCAL_ASSIGN;
VIF_ASSIGN;
__entry->new_type = type;
),
TP_printk(
LOCAL_PR_FMT VIF_PR_FMT " new type:%d",
LOCAL_PR_ARG, VIF_PR_ARG, __entry->new_type
)
);
TRACE_EVENT(drv_remove_interface, TRACE_EVENT(drv_remove_interface,
TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata), TP_PROTO(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata),
......
...@@ -472,6 +472,16 @@ enum ieee80211_sub_if_data_flags { ...@@ -472,6 +472,16 @@ enum ieee80211_sub_if_data_flags {
IEEE80211_SDATA_DONT_BRIDGE_PACKETS = BIT(3), IEEE80211_SDATA_DONT_BRIDGE_PACKETS = BIT(3),
}; };
/**
* enum ieee80211_sdata_state_bits - virtual interface state bits
* @SDATA_STATE_RUNNING: virtual interface is up & running; this
* mirrors netif_running() but is separate for interface type
* change handling while the interface is up
*/
enum ieee80211_sdata_state_bits {
SDATA_STATE_RUNNING,
};
struct ieee80211_sub_if_data { struct ieee80211_sub_if_data {
struct list_head list; struct list_head list;
...@@ -485,6 +495,8 @@ struct ieee80211_sub_if_data { ...@@ -485,6 +495,8 @@ struct ieee80211_sub_if_data {
unsigned int flags; unsigned int flags;
unsigned long state;
int drop_unencrypted; int drop_unencrypted;
char name[IFNAMSIZ]; char name[IFNAMSIZ];
...@@ -1087,7 +1099,7 @@ void ieee80211_recalc_idle(struct ieee80211_local *local); ...@@ -1087,7 +1099,7 @@ void ieee80211_recalc_idle(struct ieee80211_local *local);
static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata) static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
{ {
return netif_running(sdata->dev); return test_bit(SDATA_STATE_RUNNING, &sdata->state);
} }
/* tx handling */ /* tx handling */
......
...@@ -148,7 +148,12 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata, ...@@ -148,7 +148,12 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
return 0; return 0;
} }
static int ieee80211_open(struct net_device *dev) /*
* NOTE: Be very careful when changing this function, it must NOT return
* an error on interface type changes that have been pre-checked, so most
* checks should be in ieee80211_check_concurrent_iface.
*/
static int ieee80211_do_open(struct net_device *dev, bool coming_up)
{ {
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
...@@ -157,15 +162,6 @@ static int ieee80211_open(struct net_device *dev) ...@@ -157,15 +162,6 @@ static int ieee80211_open(struct net_device *dev)
int res; int res;
u32 hw_reconf_flags = 0; u32 hw_reconf_flags = 0;
/* fail early if user set an invalid address */
if (!is_zero_ether_addr(dev->dev_addr) &&
!is_valid_ether_addr(dev->dev_addr))
return -EADDRNOTAVAIL;
res = ieee80211_check_concurrent_iface(sdata, sdata->vif.type);
if (res)
return res;
switch (sdata->vif.type) { switch (sdata->vif.type) {
case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_WDS:
if (!is_valid_ether_addr(sdata->u.wds.remote_addr)) if (!is_valid_ether_addr(sdata->u.wds.remote_addr))
...@@ -258,9 +254,11 @@ static int ieee80211_open(struct net_device *dev) ...@@ -258,9 +254,11 @@ static int ieee80211_open(struct net_device *dev)
netif_carrier_on(dev); netif_carrier_on(dev);
break; break;
default: default:
if (coming_up) {
res = drv_add_interface(local, &sdata->vif); res = drv_add_interface(local, &sdata->vif);
if (res) if (res)
goto err_stop; goto err_stop;
}
if (ieee80211_vif_is_mesh(&sdata->vif)) { if (ieee80211_vif_is_mesh(&sdata->vif)) {
local->fif_other_bss++; local->fif_other_bss++;
...@@ -316,7 +314,9 @@ static int ieee80211_open(struct net_device *dev) ...@@ -316,7 +314,9 @@ static int ieee80211_open(struct net_device *dev)
hw_reconf_flags |= __ieee80211_recalc_idle(local); hw_reconf_flags |= __ieee80211_recalc_idle(local);
mutex_unlock(&local->mtx); mutex_unlock(&local->mtx);
if (coming_up)
local->open_count++; local->open_count++;
if (hw_reconf_flags) { if (hw_reconf_flags) {
ieee80211_hw_config(local, hw_reconf_flags); ieee80211_hw_config(local, hw_reconf_flags);
/* /*
...@@ -331,6 +331,8 @@ static int ieee80211_open(struct net_device *dev) ...@@ -331,6 +331,8 @@ static int ieee80211_open(struct net_device *dev)
netif_tx_start_all_queues(dev); netif_tx_start_all_queues(dev);
set_bit(SDATA_STATE_RUNNING, &sdata->state);
return 0; return 0;
err_del_interface: err_del_interface:
drv_remove_interface(local, &sdata->vif); drv_remove_interface(local, &sdata->vif);
...@@ -344,19 +346,38 @@ static int ieee80211_open(struct net_device *dev) ...@@ -344,19 +346,38 @@ static int ieee80211_open(struct net_device *dev)
return res; return res;
} }
static int ieee80211_stop(struct net_device *dev) static int ieee80211_open(struct net_device *dev)
{ {
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
int err;
/* fail early if user set an invalid address */
if (!is_zero_ether_addr(dev->dev_addr) &&
!is_valid_ether_addr(dev->dev_addr))
return -EADDRNOTAVAIL;
err = ieee80211_check_concurrent_iface(sdata, sdata->vif.type);
if (err)
return err;
return ieee80211_do_open(dev, true);
}
static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
bool going_down)
{
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
unsigned long flags; unsigned long flags;
struct sk_buff *skb, *tmp; struct sk_buff *skb, *tmp;
u32 hw_reconf_flags = 0; u32 hw_reconf_flags = 0;
int i; int i;
clear_bit(SDATA_STATE_RUNNING, &sdata->state);
/* /*
* Stop TX on this interface first. * Stop TX on this interface first.
*/ */
netif_tx_stop_all_queues(dev); netif_tx_stop_all_queues(sdata->dev);
/* /*
* Purge work for this interface. * Purge work for this interface.
...@@ -394,11 +415,12 @@ static int ieee80211_stop(struct net_device *dev) ...@@ -394,11 +415,12 @@ static int ieee80211_stop(struct net_device *dev)
if (sdata->vif.type == NL80211_IFTYPE_AP) if (sdata->vif.type == NL80211_IFTYPE_AP)
local->fif_pspoll--; local->fif_pspoll--;
netif_addr_lock_bh(dev); netif_addr_lock_bh(sdata->dev);
spin_lock_bh(&local->filter_lock); spin_lock_bh(&local->filter_lock);
__hw_addr_unsync(&local->mc_list, &dev->mc, dev->addr_len); __hw_addr_unsync(&local->mc_list, &sdata->dev->mc,
sdata->dev->addr_len);
spin_unlock_bh(&local->filter_lock); spin_unlock_bh(&local->filter_lock);
netif_addr_unlock_bh(dev); netif_addr_unlock_bh(sdata->dev);
ieee80211_configure_filter(local); ieee80211_configure_filter(local);
...@@ -432,6 +454,7 @@ static int ieee80211_stop(struct net_device *dev) ...@@ -432,6 +454,7 @@ static int ieee80211_stop(struct net_device *dev)
WARN_ON(!list_empty(&sdata->u.ap.vlans)); WARN_ON(!list_empty(&sdata->u.ap.vlans));
} }
if (going_down)
local->open_count--; local->open_count--;
switch (sdata->vif.type) { switch (sdata->vif.type) {
...@@ -504,6 +527,7 @@ static int ieee80211_stop(struct net_device *dev) ...@@ -504,6 +527,7 @@ static int ieee80211_stop(struct net_device *dev)
*/ */
ieee80211_free_keys(sdata); ieee80211_free_keys(sdata);
if (going_down)
drv_remove_interface(local, &sdata->vif); drv_remove_interface(local, &sdata->vif);
} }
...@@ -540,6 +564,13 @@ static int ieee80211_stop(struct net_device *dev) ...@@ -540,6 +564,13 @@ static int ieee80211_stop(struct net_device *dev)
} }
} }
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags); spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
}
static int ieee80211_stop(struct net_device *dev)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
ieee80211_do_stop(sdata, true);
return 0; return 0;
} }
...@@ -857,9 +888,72 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, ...@@ -857,9 +888,72 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
ieee80211_debugfs_add_netdev(sdata); ieee80211_debugfs_add_netdev(sdata);
} }
static int ieee80211_runtime_change_iftype(struct ieee80211_sub_if_data *sdata,
enum nl80211_iftype type)
{
struct ieee80211_local *local = sdata->local;
int ret, err;
ASSERT_RTNL();
if (!local->ops->change_interface)
return -EBUSY;
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_ADHOC:
/*
* Could maybe also all others here?
* Just not sure how that interacts
* with the RX/config path e.g. for
* mesh.
*/
break;
default:
return -EBUSY;
}
switch (type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_ADHOC:
/*
* Could probably support everything
* but WDS here (WDS do_open can fail
* under memory pressure, which this
* code isn't prepared to handle).
*/
break;
default:
return -EBUSY;
}
ret = ieee80211_check_concurrent_iface(sdata, type);
if (ret)
return ret;
ieee80211_do_stop(sdata, false);
ieee80211_teardown_sdata(sdata->dev);
ret = drv_change_interface(local, sdata, type);
if (ret)
type = sdata->vif.type;
ieee80211_setup_sdata(sdata, type);
err = ieee80211_do_open(sdata->dev, false);
WARN(err, "type change: do_open returned %d", err);
return ret;
}
int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata, int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata,
enum nl80211_iftype type) enum nl80211_iftype type)
{ {
int ret;
ASSERT_RTNL(); ASSERT_RTNL();
if (type == sdata->vif.type) if (type == sdata->vif.type)
...@@ -870,18 +964,15 @@ int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata, ...@@ -870,18 +964,15 @@ int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata,
type == NL80211_IFTYPE_ADHOC) type == NL80211_IFTYPE_ADHOC)
return -EOPNOTSUPP; return -EOPNOTSUPP;
/* if (ieee80211_sdata_running(sdata)) {
* We could, here, on changes between IBSS/STA/MESH modes, ret = ieee80211_runtime_change_iftype(sdata, type);
* invoke an MLME function instead that disassociates etc. if (ret)
* and goes into the requested mode. return ret;
*/ } else {
if (ieee80211_sdata_running(sdata))
return -EBUSY;
/* Purge and reset type-dependent state. */ /* Purge and reset type-dependent state. */
ieee80211_teardown_sdata(sdata->dev); ieee80211_teardown_sdata(sdata->dev);
ieee80211_setup_sdata(sdata, type); ieee80211_setup_sdata(sdata, type);
}
/* reset some values that shouldn't be kept across type changes */ /* reset some values that shouldn't be kept across type changes */
sdata->vif.bss_conf.basic_rates = sdata->vif.bss_conf.basic_rates =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment