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: Johannes Berg <johannes.berg@intel.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Родитель
87490f6db3
Коммит
34d4bc4d41
|
@ -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 {
|
||||||
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,
|
||||||
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,
|
||||||
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,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
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,
|
||||||
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)
|
||||||
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)
|
||||||
netif_carrier_on(dev);
|
netif_carrier_on(dev);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
res = drv_add_interface(local, &sdata->vif);
|
if (coming_up) {
|
||||||
if (res)
|
res = drv_add_interface(local, &sdata->vif);
|
||||||
goto err_stop;
|
if (res)
|
||||||
|
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)
|
||||||
hw_reconf_flags |= __ieee80211_recalc_idle(local);
|
hw_reconf_flags |= __ieee80211_recalc_idle(local);
|
||||||
mutex_unlock(&local->mtx);
|
mutex_unlock(&local->mtx);
|
||||||
|
|
||||||
local->open_count++;
|
if (coming_up)
|
||||||
|
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)
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
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,7 +454,8 @@ static int ieee80211_stop(struct net_device *dev)
|
||||||
WARN_ON(!list_empty(&sdata->u.ap.vlans));
|
WARN_ON(!list_empty(&sdata->u.ap.vlans));
|
||||||
}
|
}
|
||||||
|
|
||||||
local->open_count--;
|
if (going_down)
|
||||||
|
local->open_count--;
|
||||||
|
|
||||||
switch (sdata->vif.type) {
|
switch (sdata->vif.type) {
|
||||||
case NL80211_IFTYPE_AP_VLAN:
|
case NL80211_IFTYPE_AP_VLAN:
|
||||||
|
@ -504,7 +527,8 @@ static int ieee80211_stop(struct net_device *dev)
|
||||||
*/
|
*/
|
||||||
ieee80211_free_keys(sdata);
|
ieee80211_free_keys(sdata);
|
||||||
|
|
||||||
drv_remove_interface(local, &sdata->vif);
|
if (going_down)
|
||||||
|
drv_remove_interface(local, &sdata->vif);
|
||||||
}
|
}
|
||||||
|
|
||||||
sdata->bss = NULL;
|
sdata->bss = NULL;
|
||||||
|
@ -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,
|
||||||
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,
|
||||||
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 {
|
||||||
|
/* Purge and reset type-dependent state. */
|
||||||
if (ieee80211_sdata_running(sdata))
|
ieee80211_teardown_sdata(sdata->dev);
|
||||||
return -EBUSY;
|
ieee80211_setup_sdata(sdata, type);
|
||||||
|
}
|
||||||
/* Purge and reset type-dependent state. */
|
|
||||||
ieee80211_teardown_sdata(sdata->dev);
|
|
||||||
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 =
|
||||||
|
|
Загрузка…
Ссылка в новой задаче