2007-05-06 02:45:53 +08:00
|
|
|
/*
|
|
|
|
* mac80211 configuration hooks for cfg80211
|
|
|
|
*
|
2010-02-15 18:53:10 +08:00
|
|
|
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
|
2007-05-06 02:45:53 +08:00
|
|
|
*
|
|
|
|
* This file is GPLv2 as found in COPYING.
|
|
|
|
*/
|
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
#include <linux/ieee80211.h>
|
2007-05-06 02:45:53 +08:00
|
|
|
#include <linux/nl80211.h>
|
|
|
|
#include <linux/rtnetlink.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2007-09-18 02:56:21 +08:00
|
|
|
#include <net/net_namespace.h>
|
2007-12-19 09:03:33 +08:00
|
|
|
#include <linux/rcupdate.h>
|
2007-05-06 02:45:53 +08:00
|
|
|
#include <net/cfg80211.h>
|
|
|
|
#include "ieee80211_i.h"
|
2009-04-24 00:52:52 +08:00
|
|
|
#include "driver-ops.h"
|
2007-09-19 05:29:21 +08:00
|
|
|
#include "cfg.h"
|
2008-04-09 03:14:40 +08:00
|
|
|
#include "rate.h"
|
2008-02-23 22:17:17 +08:00
|
|
|
#include "mesh.h"
|
|
|
|
|
2008-09-11 06:01:58 +08:00
|
|
|
static bool nl80211_type_check(enum nl80211_iftype type)
|
2007-09-29 03:52:27 +08:00
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case NL80211_IFTYPE_ADHOC:
|
|
|
|
case NL80211_IFTYPE_STATION:
|
|
|
|
case NL80211_IFTYPE_MONITOR:
|
2008-02-23 22:17:17 +08:00
|
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
|
|
#endif
|
2008-10-31 01:50:30 +08:00
|
|
|
case NL80211_IFTYPE_AP:
|
|
|
|
case NL80211_IFTYPE_AP_VLAN:
|
2008-04-14 21:37:03 +08:00
|
|
|
case NL80211_IFTYPE_WDS:
|
2008-09-11 06:01:58 +08:00
|
|
|
return true;
|
2007-09-29 03:52:27 +08:00
|
|
|
default:
|
2008-09-11 06:01:58 +08:00
|
|
|
return false;
|
2007-09-29 03:52:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-11-11 03:10:05 +08:00
|
|
|
static bool nl80211_params_check(enum nl80211_iftype type,
|
|
|
|
struct vif_params *params)
|
|
|
|
{
|
|
|
|
if (!nl80211_type_check(type))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2007-05-06 02:45:53 +08:00
|
|
|
static int ieee80211_add_iface(struct wiphy *wiphy, char *name,
|
2008-02-23 22:17:06 +08:00
|
|
|
enum nl80211_iftype type, u32 *flags,
|
|
|
|
struct vif_params *params)
|
2007-05-06 02:45:53 +08:00
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
2008-02-01 02:48:23 +08:00
|
|
|
struct net_device *dev;
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
|
|
|
int err;
|
2007-05-06 02:45:53 +08:00
|
|
|
|
2009-11-11 03:10:05 +08:00
|
|
|
if (!nl80211_params_check(type, params))
|
2007-05-06 02:45:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2008-09-11 06:01:58 +08:00
|
|
|
err = ieee80211_if_add(local, name, &dev, type, params);
|
|
|
|
if (err || type != NL80211_IFTYPE_MONITOR || !flags)
|
2008-02-01 02:48:23 +08:00
|
|
|
return err;
|
|
|
|
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
sdata->u.mntr_flags = *flags;
|
|
|
|
return 0;
|
2007-05-06 02:45:53 +08:00
|
|
|
}
|
|
|
|
|
2009-07-14 06:33:35 +08:00
|
|
|
static int ieee80211_del_iface(struct wiphy *wiphy, struct net_device *dev)
|
2007-05-06 02:45:53 +08:00
|
|
|
{
|
2009-07-14 06:33:35 +08:00
|
|
|
ieee80211_if_remove(IEEE80211_DEV_TO_SUB_IF(dev));
|
2007-05-06 02:45:53 +08:00
|
|
|
|
2008-07-09 20:40:35 +08:00
|
|
|
return 0;
|
2007-05-06 02:45:53 +08:00
|
|
|
}
|
|
|
|
|
2009-06-10 03:04:43 +08:00
|
|
|
static int ieee80211_change_iface(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
2008-02-23 22:17:06 +08:00
|
|
|
enum nl80211_iftype type, u32 *flags,
|
|
|
|
struct vif_params *params)
|
2007-09-29 03:52:27 +08:00
|
|
|
{
|
2009-12-23 20:15:31 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2008-07-09 20:40:36 +08:00
|
|
|
int ret;
|
2007-09-29 03:52:27 +08:00
|
|
|
|
2009-12-23 20:15:31 +08:00
|
|
|
if (ieee80211_sdata_running(sdata))
|
2009-11-02 02:25:40 +08:00
|
|
|
return -EBUSY;
|
|
|
|
|
2009-11-11 03:10:05 +08:00
|
|
|
if (!nl80211_params_check(type, params))
|
2007-09-29 03:52:27 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2008-09-11 06:01:58 +08:00
|
|
|
ret = ieee80211_if_change_type(sdata, type);
|
2008-07-09 20:40:36 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2007-09-29 03:52:27 +08:00
|
|
|
|
2008-02-23 22:17:19 +08:00
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif) && params->mesh_id_len)
|
2008-09-11 06:01:49 +08:00
|
|
|
ieee80211_sdata_set_mesh_id(sdata,
|
|
|
|
params->mesh_id_len,
|
|
|
|
params->mesh_id);
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2009-11-19 18:55:19 +08:00
|
|
|
if (type == NL80211_IFTYPE_AP_VLAN &&
|
|
|
|
params && params->use_4addr == 0)
|
|
|
|
rcu_assign_pointer(sdata->u.vlan.sta, NULL);
|
|
|
|
else if (type == NL80211_IFTYPE_STATION &&
|
|
|
|
params && params->use_4addr >= 0)
|
|
|
|
sdata->u.mgd.use_4addr = params->use_4addr;
|
|
|
|
|
2010-04-27 06:26:34 +08:00
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_MONITOR && flags)
|
|
|
|
sdata->u.mntr_flags = *flags;
|
|
|
|
|
2007-09-29 03:52:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
|
2009-05-10 02:06:47 +08:00
|
|
|
u8 key_idx, const u8 *mac_addr,
|
2007-12-19 09:03:30 +08:00
|
|
|
struct key_params *params)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
|
|
|
struct sta_info *sta = NULL;
|
2008-02-25 23:27:45 +08:00
|
|
|
struct ieee80211_key *key;
|
2008-04-08 23:56:52 +08:00
|
|
|
int err;
|
2007-12-19 09:03:30 +08:00
|
|
|
|
2010-06-01 16:19:19 +08:00
|
|
|
if (!netif_running(dev))
|
|
|
|
return -ENETDOWN;
|
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2010-08-10 15:46:38 +08:00
|
|
|
/* reject WEP and TKIP keys if WEP failed to initialize */
|
2007-12-19 09:03:30 +08:00
|
|
|
switch (params->cipher) {
|
|
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
2010-08-10 15:46:38 +08:00
|
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
|
|
if (IS_ERR(sdata->local->wep_tx_tfm))
|
|
|
|
return -EINVAL;
|
2009-01-08 19:32:02 +08:00
|
|
|
break;
|
2007-12-19 09:03:30 +08:00
|
|
|
default:
|
2010-08-10 15:46:38 +08:00
|
|
|
break;
|
2007-12-19 09:03:30 +08:00
|
|
|
}
|
|
|
|
|
2010-08-10 15:46:38 +08:00
|
|
|
key = ieee80211_key_alloc(params->cipher, key_idx, params->key_len,
|
|
|
|
params->key, params->seq_len, params->seq);
|
2010-08-02 00:37:03 +08:00
|
|
|
if (IS_ERR(key))
|
|
|
|
return PTR_ERR(key);
|
2008-02-25 23:27:45 +08:00
|
|
|
|
2010-06-01 16:19:19 +08:00
|
|
|
mutex_lock(&sdata->local->sta_mtx);
|
2008-04-08 23:56:52 +08:00
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
if (mac_addr) {
|
2010-01-09 01:10:58 +08:00
|
|
|
sta = sta_info_get_bss(sdata, mac_addr);
|
2008-02-25 23:27:45 +08:00
|
|
|
if (!sta) {
|
2010-07-27 06:52:03 +08:00
|
|
|
ieee80211_key_free(sdata->local, key);
|
2008-04-08 23:56:52 +08:00
|
|
|
err = -ENOENT;
|
|
|
|
goto out_unlock;
|
2008-02-25 23:27:45 +08:00
|
|
|
}
|
2007-12-19 09:03:30 +08:00
|
|
|
}
|
|
|
|
|
2008-02-25 23:27:45 +08:00
|
|
|
ieee80211_key_link(key, sdata, sta);
|
|
|
|
|
2008-04-08 23:56:52 +08:00
|
|
|
err = 0;
|
|
|
|
out_unlock:
|
2010-06-01 16:19:19 +08:00
|
|
|
mutex_unlock(&sdata->local->sta_mtx);
|
2008-04-08 23:56:52 +08:00
|
|
|
|
|
|
|
return err;
|
2007-12-19 09:03:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
|
2009-05-10 02:06:47 +08:00
|
|
|
u8 key_idx, const u8 *mac_addr)
|
2007-12-19 09:03:30 +08:00
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
|
|
|
struct sta_info *sta;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2010-06-01 16:19:19 +08:00
|
|
|
mutex_lock(&sdata->local->sta_mtx);
|
2008-04-08 23:56:52 +08:00
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
if (mac_addr) {
|
2008-04-08 23:56:52 +08:00
|
|
|
ret = -ENOENT;
|
|
|
|
|
2010-01-09 01:10:58 +08:00
|
|
|
sta = sta_info_get_bss(sdata, mac_addr);
|
2007-12-19 09:03:30 +08:00
|
|
|
if (!sta)
|
2008-04-08 23:56:52 +08:00
|
|
|
goto out_unlock;
|
2007-12-19 09:03:30 +08:00
|
|
|
|
2008-02-25 23:27:45 +08:00
|
|
|
if (sta->key) {
|
2010-07-27 06:52:03 +08:00
|
|
|
ieee80211_key_free(sdata->local, sta->key);
|
2008-02-25 23:27:45 +08:00
|
|
|
WARN_ON(sta->key);
|
2008-04-08 23:56:52 +08:00
|
|
|
ret = 0;
|
|
|
|
}
|
2007-12-19 09:03:30 +08:00
|
|
|
|
2008-04-08 23:56:52 +08:00
|
|
|
goto out_unlock;
|
2007-12-19 09:03:30 +08:00
|
|
|
}
|
|
|
|
|
2008-04-08 23:56:52 +08:00
|
|
|
if (!sdata->keys[key_idx]) {
|
|
|
|
ret = -ENOENT;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
2007-12-19 09:03:30 +08:00
|
|
|
|
2010-07-27 06:52:03 +08:00
|
|
|
ieee80211_key_free(sdata->local, sdata->keys[key_idx]);
|
2008-02-25 23:27:45 +08:00
|
|
|
WARN_ON(sdata->keys[key_idx]);
|
2007-12-19 09:03:30 +08:00
|
|
|
|
2008-04-08 23:56:52 +08:00
|
|
|
ret = 0;
|
|
|
|
out_unlock:
|
2010-06-01 16:19:19 +08:00
|
|
|
mutex_unlock(&sdata->local->sta_mtx);
|
2008-04-08 23:56:52 +08:00
|
|
|
|
|
|
|
return ret;
|
2007-12-19 09:03:30 +08:00
|
|
|
}
|
|
|
|
|
2007-12-19 09:03:31 +08:00
|
|
|
static int ieee80211_get_key(struct wiphy *wiphy, struct net_device *dev,
|
2009-05-10 02:06:47 +08:00
|
|
|
u8 key_idx, const u8 *mac_addr, void *cookie,
|
2007-12-19 09:03:31 +08:00
|
|
|
void (*callback)(void *cookie,
|
|
|
|
struct key_params *params))
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2007-12-19 09:03:31 +08:00
|
|
|
struct sta_info *sta = NULL;
|
|
|
|
u8 seq[6] = {0};
|
|
|
|
struct key_params params;
|
|
|
|
struct ieee80211_key *key;
|
|
|
|
u32 iv32;
|
|
|
|
u16 iv16;
|
|
|
|
int err = -ENOENT;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2008-04-08 23:56:52 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
|
2007-12-19 09:03:31 +08:00
|
|
|
if (mac_addr) {
|
2010-01-09 01:10:58 +08:00
|
|
|
sta = sta_info_get_bss(sdata, mac_addr);
|
2007-12-19 09:03:31 +08:00
|
|
|
if (!sta)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
key = sta->key;
|
|
|
|
} else
|
|
|
|
key = sdata->keys[key_idx];
|
|
|
|
|
|
|
|
if (!key)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
|
2010-08-10 15:46:38 +08:00
|
|
|
params.cipher = key->conf.cipher;
|
2007-12-19 09:03:31 +08:00
|
|
|
|
2010-08-10 15:46:38 +08:00
|
|
|
switch (key->conf.cipher) {
|
|
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
2008-05-15 07:26:19 +08:00
|
|
|
iv32 = key->u.tkip.tx.iv32;
|
|
|
|
iv16 = key->u.tkip.tx.iv16;
|
2007-12-19 09:03:31 +08:00
|
|
|
|
2009-04-24 00:52:52 +08:00
|
|
|
if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
|
|
|
|
drv_get_tkip_seq(sdata->local,
|
|
|
|
key->conf.hw_key_idx,
|
|
|
|
&iv32, &iv16);
|
2007-12-19 09:03:31 +08:00
|
|
|
|
|
|
|
seq[0] = iv16 & 0xff;
|
|
|
|
seq[1] = (iv16 >> 8) & 0xff;
|
|
|
|
seq[2] = iv32 & 0xff;
|
|
|
|
seq[3] = (iv32 >> 8) & 0xff;
|
|
|
|
seq[4] = (iv32 >> 16) & 0xff;
|
|
|
|
seq[5] = (iv32 >> 24) & 0xff;
|
|
|
|
params.seq = seq;
|
|
|
|
params.seq_len = 6;
|
|
|
|
break;
|
2010-08-10 15:46:38 +08:00
|
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
2007-12-19 09:03:31 +08:00
|
|
|
seq[0] = key->u.ccmp.tx_pn[5];
|
|
|
|
seq[1] = key->u.ccmp.tx_pn[4];
|
|
|
|
seq[2] = key->u.ccmp.tx_pn[3];
|
|
|
|
seq[3] = key->u.ccmp.tx_pn[2];
|
|
|
|
seq[4] = key->u.ccmp.tx_pn[1];
|
|
|
|
seq[5] = key->u.ccmp.tx_pn[0];
|
|
|
|
params.seq = seq;
|
|
|
|
params.seq_len = 6;
|
|
|
|
break;
|
2010-08-10 15:46:38 +08:00
|
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
2009-01-08 19:32:02 +08:00
|
|
|
seq[0] = key->u.aes_cmac.tx_pn[5];
|
|
|
|
seq[1] = key->u.aes_cmac.tx_pn[4];
|
|
|
|
seq[2] = key->u.aes_cmac.tx_pn[3];
|
|
|
|
seq[3] = key->u.aes_cmac.tx_pn[2];
|
|
|
|
seq[4] = key->u.aes_cmac.tx_pn[1];
|
|
|
|
seq[5] = key->u.aes_cmac.tx_pn[0];
|
|
|
|
params.seq = seq;
|
|
|
|
params.seq_len = 6;
|
|
|
|
break;
|
2007-12-19 09:03:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
params.key = key->conf.key;
|
|
|
|
params.key_len = key->conf.keylen;
|
|
|
|
|
|
|
|
callback(cookie, ¶ms);
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
out:
|
2008-04-08 23:56:52 +08:00
|
|
|
rcu_read_unlock();
|
2007-12-19 09:03:31 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
static int ieee80211_config_default_key(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
u8 key_idx)
|
|
|
|
{
|
2010-06-01 16:19:19 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2008-04-08 23:56:52 +08:00
|
|
|
|
2007-12-19 09:03:30 +08:00
|
|
|
ieee80211_set_default_key(sdata, key_idx);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-01-08 19:32:02 +08:00
|
|
|
static int ieee80211_config_default_mgmt_key(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
u8 key_idx)
|
|
|
|
{
|
2010-07-22 19:58:51 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2009-01-08 19:32:02 +08:00
|
|
|
|
|
|
|
ieee80211_set_default_mgmt_key(sdata, key_idx);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
|
|
|
|
{
|
2008-02-25 23:27:46 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2009-08-07 22:17:38 +08:00
|
|
|
sinfo->generation = sdata->local->sta_generation;
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
sinfo->filled = STATION_INFO_INACTIVE_TIME |
|
|
|
|
STATION_INFO_RX_BYTES |
|
2008-12-12 05:04:19 +08:00
|
|
|
STATION_INFO_TX_BYTES |
|
2009-02-17 19:24:57 +08:00
|
|
|
STATION_INFO_RX_PACKETS |
|
|
|
|
STATION_INFO_TX_PACKETS |
|
2008-12-12 05:04:19 +08:00
|
|
|
STATION_INFO_TX_BITRATE;
|
2008-02-23 22:17:17 +08:00
|
|
|
|
|
|
|
sinfo->inactive_time = jiffies_to_msecs(jiffies - sta->last_rx);
|
|
|
|
sinfo->rx_bytes = sta->rx_bytes;
|
|
|
|
sinfo->tx_bytes = sta->tx_bytes;
|
2009-02-17 19:24:57 +08:00
|
|
|
sinfo->rx_packets = sta->rx_packets;
|
|
|
|
sinfo->tx_packets = sta->tx_packets;
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2009-12-09 06:10:13 +08:00
|
|
|
if ((sta->local->hw.flags & IEEE80211_HW_SIGNAL_DBM) ||
|
|
|
|
(sta->local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC)) {
|
2008-12-12 05:04:19 +08:00
|
|
|
sinfo->filled |= STATION_INFO_SIGNAL;
|
|
|
|
sinfo->signal = (s8)sta->last_signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
sinfo->txrate.flags = 0;
|
|
|
|
if (sta->last_tx_rate.flags & IEEE80211_TX_RC_MCS)
|
|
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_MCS;
|
|
|
|
if (sta->last_tx_rate.flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
|
|
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH;
|
|
|
|
if (sta->last_tx_rate.flags & IEEE80211_TX_RC_SHORT_GI)
|
|
|
|
sinfo->txrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
|
|
|
|
|
|
|
|
if (!(sta->last_tx_rate.flags & IEEE80211_TX_RC_MCS)) {
|
|
|
|
struct ieee80211_supported_band *sband;
|
|
|
|
sband = sta->local->hw.wiphy->bands[
|
|
|
|
sta->local->hw.conf.channel->band];
|
|
|
|
sinfo->txrate.legacy =
|
|
|
|
sband->bitrates[sta->last_tx_rate.idx].bitrate;
|
|
|
|
} else
|
|
|
|
sinfo->txrate.mcs = sta->last_tx_rate.idx;
|
|
|
|
|
2008-02-23 22:17:19 +08:00
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
2008-02-23 22:17:17 +08:00
|
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
|
|
sinfo->filled |= STATION_INFO_LLID |
|
|
|
|
STATION_INFO_PLID |
|
|
|
|
STATION_INFO_PLINK_STATE;
|
|
|
|
|
|
|
|
sinfo->llid = le16_to_cpu(sta->llid);
|
|
|
|
sinfo->plid = le16_to_cpu(sta->plid);
|
|
|
|
sinfo->plink_state = sta->plink_state;
|
|
|
|
#endif
|
2008-02-23 22:17:19 +08:00
|
|
|
}
|
2008-02-23 22:17:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int ieee80211_dump_station(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
int idx, u8 *mac, struct station_info *sinfo)
|
|
|
|
{
|
2009-11-16 19:00:37 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2008-02-23 22:17:17 +08:00
|
|
|
struct sta_info *sta;
|
2008-02-25 23:27:46 +08:00
|
|
|
int ret = -ENOENT;
|
|
|
|
|
|
|
|
rcu_read_lock();
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2009-11-16 19:00:37 +08:00
|
|
|
sta = sta_info_get_by_idx(sdata, idx);
|
2008-02-25 23:27:46 +08:00
|
|
|
if (sta) {
|
|
|
|
ret = 0;
|
2008-09-11 06:02:02 +08:00
|
|
|
memcpy(mac, sta->sta.addr, ETH_ALEN);
|
2008-02-25 23:27:46 +08:00
|
|
|
sta_set_sinfo(sta, sinfo);
|
|
|
|
}
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
rcu_read_unlock();
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
return ret;
|
2008-02-23 22:17:17 +08:00
|
|
|
}
|
|
|
|
|
2010-04-19 16:23:57 +08:00
|
|
|
static int ieee80211_dump_survey(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
int idx, struct survey_info *survey)
|
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
|
|
|
|
|
|
|
|
return drv_get_survey(local, idx, survey);
|
|
|
|
}
|
|
|
|
|
2007-12-19 09:03:37 +08:00
|
|
|
static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev,
|
2008-02-23 22:17:06 +08:00
|
|
|
u8 *mac, struct station_info *sinfo)
|
2007-12-19 09:03:37 +08:00
|
|
|
{
|
2009-11-26 00:46:18 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2007-12-19 09:03:37 +08:00
|
|
|
struct sta_info *sta;
|
2008-02-25 23:27:46 +08:00
|
|
|
int ret = -ENOENT;
|
2007-12-19 09:03:37 +08:00
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
rcu_read_lock();
|
2007-12-19 09:03:37 +08:00
|
|
|
|
2010-01-09 01:10:58 +08:00
|
|
|
sta = sta_info_get_bss(sdata, mac);
|
2008-02-25 23:27:46 +08:00
|
|
|
if (sta) {
|
|
|
|
ret = 0;
|
|
|
|
sta_set_sinfo(sta, sinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
|
|
return ret;
|
2007-12-19 09:03:37 +08:00
|
|
|
}
|
|
|
|
|
2007-12-19 09:03:33 +08:00
|
|
|
/*
|
|
|
|
* This handles both adding a beacon and setting new beacon info
|
|
|
|
*/
|
|
|
|
static int ieee80211_config_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
|
struct beacon_parameters *params)
|
|
|
|
{
|
|
|
|
struct beacon_data *new, *old;
|
|
|
|
int new_head_len, new_tail_len;
|
|
|
|
int size;
|
|
|
|
int err = -EINVAL;
|
|
|
|
|
|
|
|
old = sdata->u.ap.beacon;
|
|
|
|
|
|
|
|
/* head must not be zero-length */
|
|
|
|
if (params->head && !params->head_len)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a kludge. beacon interval should really be part
|
|
|
|
* of the beacon information.
|
|
|
|
*/
|
2009-04-23 22:10:04 +08:00
|
|
|
if (params->interval &&
|
|
|
|
(sdata->vif.bss_conf.beacon_int != params->interval)) {
|
|
|
|
sdata->vif.bss_conf.beacon_int = params->interval;
|
|
|
|
ieee80211_bss_info_change_notify(sdata,
|
|
|
|
BSS_CHANGED_BEACON_INT);
|
2007-12-19 09:03:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Need to have a beacon head if we don't have one yet */
|
|
|
|
if (!params->head && !old)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* sorry, no way to start beaconing without dtim period */
|
|
|
|
if (!params->dtim_period && !old)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* new or old head? */
|
|
|
|
if (params->head)
|
|
|
|
new_head_len = params->head_len;
|
|
|
|
else
|
|
|
|
new_head_len = old->head_len;
|
|
|
|
|
|
|
|
/* new or old tail? */
|
|
|
|
if (params->tail || !old)
|
|
|
|
/* params->tail_len will be zero for !params->tail */
|
|
|
|
new_tail_len = params->tail_len;
|
|
|
|
else
|
|
|
|
new_tail_len = old->tail_len;
|
|
|
|
|
|
|
|
size = sizeof(*new) + new_head_len + new_tail_len;
|
|
|
|
|
|
|
|
new = kzalloc(size, GFP_KERNEL);
|
|
|
|
if (!new)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* start filling the new info now */
|
|
|
|
|
|
|
|
/* new or old dtim period? */
|
|
|
|
if (params->dtim_period)
|
|
|
|
new->dtim_period = params->dtim_period;
|
|
|
|
else
|
|
|
|
new->dtim_period = old->dtim_period;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* pointers go into the block we allocated,
|
|
|
|
* memory is | beacon_data | head | tail |
|
|
|
|
*/
|
|
|
|
new->head = ((u8 *) new) + sizeof(*new);
|
|
|
|
new->tail = new->head + new_head_len;
|
|
|
|
new->head_len = new_head_len;
|
|
|
|
new->tail_len = new_tail_len;
|
|
|
|
|
|
|
|
/* copy in head */
|
|
|
|
if (params->head)
|
|
|
|
memcpy(new->head, params->head, new_head_len);
|
|
|
|
else
|
|
|
|
memcpy(new->head, old->head, new_head_len);
|
|
|
|
|
|
|
|
/* copy in optional tail */
|
|
|
|
if (params->tail)
|
|
|
|
memcpy(new->tail, params->tail, new_tail_len);
|
|
|
|
else
|
|
|
|
if (old)
|
|
|
|
memcpy(new->tail, old->tail, new_tail_len);
|
|
|
|
|
2010-02-05 18:45:06 +08:00
|
|
|
sdata->vif.bss_conf.dtim_period = new->dtim_period;
|
|
|
|
|
2007-12-19 09:03:33 +08:00
|
|
|
rcu_assign_pointer(sdata->u.ap.beacon, new);
|
|
|
|
|
|
|
|
synchronize_rcu();
|
|
|
|
|
|
|
|
kfree(old);
|
|
|
|
|
2009-04-23 22:13:26 +08:00
|
|
|
ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED |
|
|
|
|
BSS_CHANGED_BEACON);
|
|
|
|
return 0;
|
2007-12-19 09:03:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_add_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
struct beacon_parameters *params)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2007-12-19 09:03:33 +08:00
|
|
|
struct beacon_data *old;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2007-12-19 09:03:33 +08:00
|
|
|
old = sdata->u.ap.beacon;
|
|
|
|
|
|
|
|
if (old)
|
|
|
|
return -EALREADY;
|
|
|
|
|
|
|
|
return ieee80211_config_beacon(sdata, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_set_beacon(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
struct beacon_parameters *params)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2007-12-19 09:03:33 +08:00
|
|
|
struct beacon_data *old;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2007-12-19 09:03:33 +08:00
|
|
|
old = sdata->u.ap.beacon;
|
|
|
|
|
|
|
|
if (!old)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
return ieee80211_config_beacon(sdata, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_del_beacon(struct wiphy *wiphy, struct net_device *dev)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2007-12-19 09:03:33 +08:00
|
|
|
struct beacon_data *old;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2007-12-19 09:03:33 +08:00
|
|
|
old = sdata->u.ap.beacon;
|
|
|
|
|
|
|
|
if (!old)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
rcu_assign_pointer(sdata->u.ap.beacon, NULL);
|
|
|
|
synchronize_rcu();
|
|
|
|
kfree(old);
|
|
|
|
|
2009-04-23 22:13:26 +08:00
|
|
|
ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED);
|
|
|
|
return 0;
|
2007-12-19 09:03:33 +08:00
|
|
|
}
|
|
|
|
|
2007-12-19 09:03:35 +08:00
|
|
|
/* Layer 2 Update frame (802.2 Type 1 LLC XID Update response) */
|
|
|
|
struct iapp_layer2_update {
|
|
|
|
u8 da[ETH_ALEN]; /* broadcast */
|
|
|
|
u8 sa[ETH_ALEN]; /* STA addr */
|
|
|
|
__be16 len; /* 6 */
|
|
|
|
u8 dsap; /* 0 */
|
|
|
|
u8 ssap; /* 0 */
|
|
|
|
u8 control;
|
|
|
|
u8 xid_info[3];
|
2010-06-03 18:21:52 +08:00
|
|
|
} __packed;
|
2007-12-19 09:03:35 +08:00
|
|
|
|
|
|
|
static void ieee80211_send_layer2_update(struct sta_info *sta)
|
|
|
|
{
|
|
|
|
struct iapp_layer2_update *msg;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
|
|
|
|
/* Send Level 2 Update Frame to update forwarding tables in layer 2
|
|
|
|
* bridge devices */
|
|
|
|
|
|
|
|
skb = dev_alloc_skb(sizeof(*msg));
|
|
|
|
if (!skb)
|
|
|
|
return;
|
|
|
|
msg = (struct iapp_layer2_update *)skb_put(skb, sizeof(*msg));
|
|
|
|
|
|
|
|
/* 802.2 Type 1 Logical Link Control (LLC) Exchange Identifier (XID)
|
|
|
|
* Update response frame; IEEE Std 802.2-1998, 5.4.1.2.1 */
|
|
|
|
|
|
|
|
memset(msg->da, 0xff, ETH_ALEN);
|
2008-09-11 06:02:02 +08:00
|
|
|
memcpy(msg->sa, sta->sta.addr, ETH_ALEN);
|
2007-12-19 09:03:35 +08:00
|
|
|
msg->len = htons(6);
|
|
|
|
msg->dsap = 0;
|
|
|
|
msg->ssap = 0x01; /* NULL LSAP, CR Bit: Response */
|
|
|
|
msg->control = 0xaf; /* XID response lsb.1111F101.
|
|
|
|
* F=0 (no poll command; unsolicited frame) */
|
|
|
|
msg->xid_info[0] = 0x81; /* XID format identifier */
|
|
|
|
msg->xid_info[1] = 1; /* LLC types/classes: Type 1 LLC */
|
|
|
|
msg->xid_info[2] = 0; /* XID sender's receive window size (RW) */
|
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
skb->dev = sta->sdata->dev;
|
|
|
|
skb->protocol = eth_type_trans(skb, sta->sdata->dev);
|
2007-12-19 09:03:35 +08:00
|
|
|
memset(skb->cb, 0, sizeof(skb->cb));
|
2010-07-19 23:52:59 +08:00
|
|
|
netif_rx_ni(skb);
|
2007-12-19 09:03:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sta_apply_parameters(struct ieee80211_local *local,
|
|
|
|
struct sta_info *sta,
|
|
|
|
struct station_parameters *params)
|
|
|
|
{
|
|
|
|
u32 rates;
|
|
|
|
int i, j;
|
2008-01-25 02:38:38 +08:00
|
|
|
struct ieee80211_supported_band *sband;
|
2008-02-25 23:27:46 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
2009-05-12 02:57:56 +08:00
|
|
|
u32 mask, set;
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2008-10-14 22:58:37 +08:00
|
|
|
sband = local->hw.wiphy->bands[local->oper_channel->band];
|
|
|
|
|
2009-05-12 02:57:56 +08:00
|
|
|
spin_lock_bh(&sta->lock);
|
|
|
|
mask = params->sta_flags_mask;
|
|
|
|
set = params->sta_flags_set;
|
2008-02-25 23:27:47 +08:00
|
|
|
|
2009-05-12 02:57:56 +08:00
|
|
|
if (mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
|
2007-12-19 09:03:35 +08:00
|
|
|
sta->flags &= ~WLAN_STA_AUTHORIZED;
|
2009-05-12 02:57:56 +08:00
|
|
|
if (set & BIT(NL80211_STA_FLAG_AUTHORIZED))
|
2007-12-19 09:03:35 +08:00
|
|
|
sta->flags |= WLAN_STA_AUTHORIZED;
|
2009-05-12 02:57:56 +08:00
|
|
|
}
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2009-05-12 02:57:56 +08:00
|
|
|
if (mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
|
2007-12-19 09:03:35 +08:00
|
|
|
sta->flags &= ~WLAN_STA_SHORT_PREAMBLE;
|
2009-05-12 02:57:56 +08:00
|
|
|
if (set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
|
2007-12-19 09:03:35 +08:00
|
|
|
sta->flags |= WLAN_STA_SHORT_PREAMBLE;
|
2009-05-12 02:57:56 +08:00
|
|
|
}
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2009-05-12 02:57:56 +08:00
|
|
|
if (mask & BIT(NL80211_STA_FLAG_WME)) {
|
2007-12-19 09:03:35 +08:00
|
|
|
sta->flags &= ~WLAN_STA_WME;
|
2009-05-12 02:57:56 +08:00
|
|
|
if (set & BIT(NL80211_STA_FLAG_WME))
|
2007-12-19 09:03:35 +08:00
|
|
|
sta->flags |= WLAN_STA_WME;
|
2009-05-12 02:57:56 +08:00
|
|
|
}
|
2009-01-08 19:31:59 +08:00
|
|
|
|
2009-05-12 02:57:56 +08:00
|
|
|
if (mask & BIT(NL80211_STA_FLAG_MFP)) {
|
2009-01-08 19:31:59 +08:00
|
|
|
sta->flags &= ~WLAN_STA_MFP;
|
2009-05-12 02:57:56 +08:00
|
|
|
if (set & BIT(NL80211_STA_FLAG_MFP))
|
2009-01-08 19:31:59 +08:00
|
|
|
sta->flags |= WLAN_STA_MFP;
|
2007-12-19 09:03:35 +08:00
|
|
|
}
|
2009-05-12 02:57:56 +08:00
|
|
|
spin_unlock_bh(&sta->lock);
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2009-05-24 22:42:30 +08:00
|
|
|
/*
|
|
|
|
* cfg80211 validates this (1-2007) and allows setting the AID
|
|
|
|
* only when creating a new station entry
|
|
|
|
*/
|
|
|
|
if (params->aid)
|
|
|
|
sta->sta.aid = params->aid;
|
|
|
|
|
2008-02-25 23:27:47 +08:00
|
|
|
/*
|
|
|
|
* FIXME: updating the following information is racy when this
|
|
|
|
* function is called from ieee80211_change_station().
|
|
|
|
* However, all this information should be static so
|
|
|
|
* maybe we should just reject attemps to change it.
|
|
|
|
*/
|
|
|
|
|
2007-12-19 09:03:35 +08:00
|
|
|
if (params->listen_interval >= 0)
|
|
|
|
sta->listen_interval = params->listen_interval;
|
|
|
|
|
|
|
|
if (params->supported_rates) {
|
|
|
|
rates = 0;
|
2008-01-25 02:38:38 +08:00
|
|
|
|
2007-12-19 09:03:35 +08:00
|
|
|
for (i = 0; i < params->supported_rates_len; i++) {
|
|
|
|
int rate = (params->supported_rates[i] & 0x7f) * 5;
|
2008-01-25 02:38:38 +08:00
|
|
|
for (j = 0; j < sband->n_bitrates; j++) {
|
|
|
|
if (sband->bitrates[j].bitrate == rate)
|
2007-12-19 09:03:35 +08:00
|
|
|
rates |= BIT(j);
|
|
|
|
}
|
|
|
|
}
|
2008-09-11 08:45:11 +08:00
|
|
|
sta->sta.supp_rates[local->oper_channel->band] = rates;
|
2007-12-19 09:03:35 +08:00
|
|
|
}
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-10-09 18:13:49 +08:00
|
|
|
if (params->ht_capa)
|
2008-10-14 22:58:37 +08:00
|
|
|
ieee80211_ht_cap_ie_to_sta_ht_cap(sband,
|
|
|
|
params->ht_capa,
|
2008-10-09 18:13:49 +08:00
|
|
|
&sta->sta.ht_cap);
|
2008-08-25 16:58:58 +08:00
|
|
|
|
2008-02-23 22:17:19 +08:00
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif) && params->plink_action) {
|
2008-02-23 22:17:17 +08:00
|
|
|
switch (params->plink_action) {
|
|
|
|
case PLINK_ACTION_OPEN:
|
|
|
|
mesh_plink_open(sta);
|
|
|
|
break;
|
|
|
|
case PLINK_ACTION_BLOCK:
|
|
|
|
mesh_plink_block(sta);
|
|
|
|
break;
|
|
|
|
}
|
2008-02-23 22:17:19 +08:00
|
|
|
}
|
2007-12-19 09:03:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
u8 *mac, struct station_parameters *params)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
2007-12-19 09:03:35 +08:00
|
|
|
struct sta_info *sta;
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2008-02-25 23:27:47 +08:00
|
|
|
int err;
|
2008-12-12 23:08:31 +08:00
|
|
|
int layer2_update;
|
2007-12-19 09:03:35 +08:00
|
|
|
|
|
|
|
if (params->vlan) {
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
|
|
|
|
|
2008-09-11 06:01:58 +08:00
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
|
|
|
|
sdata->vif.type != NL80211_IFTYPE_AP)
|
2007-12-19 09:03:35 +08:00
|
|
|
return -EINVAL;
|
|
|
|
} else
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2009-11-26 00:46:19 +08:00
|
|
|
if (compare_ether_addr(mac, sdata->vif.addr) == 0)
|
2008-02-27 16:56:40 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (is_multicast_ether_addr(mac))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
sta = sta_info_alloc(sdata, mac, GFP_KERNEL);
|
2008-02-25 23:27:47 +08:00
|
|
|
if (!sta)
|
|
|
|
return -ENOMEM;
|
2007-12-19 09:03:35 +08:00
|
|
|
|
|
|
|
sta->flags = WLAN_STA_AUTH | WLAN_STA_ASSOC;
|
|
|
|
|
|
|
|
sta_apply_parameters(local, sta, params);
|
|
|
|
|
2008-09-19 00:14:18 +08:00
|
|
|
rate_control_rate_init(sta);
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2008-12-12 23:08:31 +08:00
|
|
|
layer2_update = sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
|
|
|
|
sdata->vif.type == NL80211_IFTYPE_AP;
|
|
|
|
|
2010-02-03 20:59:58 +08:00
|
|
|
err = sta_info_insert_rcu(sta);
|
2008-02-25 23:27:47 +08:00
|
|
|
if (err) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2008-12-12 23:08:31 +08:00
|
|
|
if (layer2_update)
|
2008-02-25 23:27:47 +08:00
|
|
|
ieee80211_send_layer2_update(sta);
|
|
|
|
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
2007-12-19 09:03:35 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
u8 *mac)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2010-02-03 20:59:58 +08:00
|
|
|
if (mac)
|
|
|
|
return sta_info_destroy_addr_bss(sdata, mac);
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2010-02-03 20:59:58 +08:00
|
|
|
sta_info_flush(local, sdata);
|
2007-12-19 09:03:35 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_change_station(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
u8 *mac,
|
|
|
|
struct station_parameters *params)
|
|
|
|
{
|
2009-11-26 00:46:18 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
2007-12-19 09:03:35 +08:00
|
|
|
struct sta_info *sta;
|
|
|
|
struct ieee80211_sub_if_data *vlansdata;
|
|
|
|
|
2008-04-10 21:36:09 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
|
2010-01-09 01:10:58 +08:00
|
|
|
sta = sta_info_get_bss(sdata, mac);
|
2008-04-10 21:36:09 +08:00
|
|
|
if (!sta) {
|
|
|
|
rcu_read_unlock();
|
2007-12-19 09:03:35 +08:00
|
|
|
return -ENOENT;
|
2008-04-10 21:36:09 +08:00
|
|
|
}
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
if (params->vlan && params->vlan != sta->sdata->dev) {
|
2007-12-19 09:03:35 +08:00
|
|
|
vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
|
|
|
|
|
2008-09-11 06:01:58 +08:00
|
|
|
if (vlansdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
|
|
|
|
vlansdata->vif.type != NL80211_IFTYPE_AP) {
|
2008-04-10 21:36:09 +08:00
|
|
|
rcu_read_unlock();
|
2007-12-19 09:03:35 +08:00
|
|
|
return -EINVAL;
|
2008-04-10 21:36:09 +08:00
|
|
|
}
|
2007-12-19 09:03:35 +08:00
|
|
|
|
2009-11-19 18:55:19 +08:00
|
|
|
if (params->vlan->ieee80211_ptr->use_4addr) {
|
2009-11-20 17:09:14 +08:00
|
|
|
if (vlansdata->u.vlan.sta) {
|
|
|
|
rcu_read_unlock();
|
2009-11-11 03:10:05 +08:00
|
|
|
return -EBUSY;
|
2009-11-20 17:09:14 +08:00
|
|
|
}
|
2009-11-11 03:10:05 +08:00
|
|
|
|
|
|
|
rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
|
|
|
|
}
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sta->sdata = vlansdata;
|
2007-12-19 09:03:35 +08:00
|
|
|
ieee80211_send_layer2_update(sta);
|
|
|
|
}
|
|
|
|
|
|
|
|
sta_apply_parameters(local, sta, params);
|
|
|
|
|
2008-04-10 21:36:09 +08:00
|
|
|
rcu_read_unlock();
|
|
|
|
|
2007-12-19 09:03:35 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
|
|
static int ieee80211_add_mpath(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
u8 *dst, u8 *next_hop)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2008-02-23 22:17:17 +08:00
|
|
|
struct mesh_path *mpath;
|
|
|
|
struct sta_info *sta;
|
|
|
|
int err;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
rcu_read_lock();
|
2009-11-26 00:46:18 +08:00
|
|
|
sta = sta_info_get(sdata, next_hop);
|
2008-02-25 23:27:46 +08:00
|
|
|
if (!sta) {
|
|
|
|
rcu_read_unlock();
|
2008-02-23 22:17:17 +08:00
|
|
|
return -ENOENT;
|
2008-02-25 23:27:46 +08:00
|
|
|
}
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-08-03 08:04:37 +08:00
|
|
|
err = mesh_path_add(dst, sdata);
|
2008-02-25 23:27:46 +08:00
|
|
|
if (err) {
|
|
|
|
rcu_read_unlock();
|
2008-02-23 22:17:17 +08:00
|
|
|
return err;
|
2008-02-25 23:27:46 +08:00
|
|
|
}
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-08-03 08:04:37 +08:00
|
|
|
mpath = mesh_path_lookup(dst, sdata);
|
2008-02-23 22:17:17 +08:00
|
|
|
if (!mpath) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
mesh_path_fix_nexthop(mpath, sta);
|
2008-02-25 23:27:46 +08:00
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_del_mpath(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
u8 *dst)
|
|
|
|
{
|
2008-08-03 08:04:37 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
if (dst)
|
2008-08-03 08:04:37 +08:00
|
|
|
return mesh_path_del(dst, sdata);
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-08-03 08:04:37 +08:00
|
|
|
mesh_path_flush(sdata);
|
2008-02-23 22:17:17 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_change_mpath(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
u8 *dst, u8 *next_hop)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2008-02-23 22:17:17 +08:00
|
|
|
struct mesh_path *mpath;
|
|
|
|
struct sta_info *sta;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2008-02-25 23:27:46 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
|
2009-11-26 00:46:18 +08:00
|
|
|
sta = sta_info_get(sdata, next_hop);
|
2008-02-25 23:27:46 +08:00
|
|
|
if (!sta) {
|
|
|
|
rcu_read_unlock();
|
2008-02-23 22:17:17 +08:00
|
|
|
return -ENOENT;
|
2008-02-25 23:27:46 +08:00
|
|
|
}
|
2008-02-23 22:17:17 +08:00
|
|
|
|
2008-08-03 08:04:37 +08:00
|
|
|
mpath = mesh_path_lookup(dst, sdata);
|
2008-02-23 22:17:17 +08:00
|
|
|
if (!mpath) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
mesh_path_fix_nexthop(mpath, sta);
|
2008-02-25 23:27:46 +08:00
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mpath_set_pinfo(struct mesh_path *mpath, u8 *next_hop,
|
|
|
|
struct mpath_info *pinfo)
|
|
|
|
{
|
|
|
|
if (mpath->next_hop)
|
2008-09-11 06:02:02 +08:00
|
|
|
memcpy(next_hop, mpath->next_hop->sta.addr, ETH_ALEN);
|
2008-02-23 22:17:17 +08:00
|
|
|
else
|
|
|
|
memset(next_hop, 0, ETH_ALEN);
|
|
|
|
|
2009-08-07 22:17:38 +08:00
|
|
|
pinfo->generation = mesh_paths_generation;
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
pinfo->filled = MPATH_INFO_FRAME_QLEN |
|
2009-11-10 07:46:55 +08:00
|
|
|
MPATH_INFO_SN |
|
2008-02-23 22:17:17 +08:00
|
|
|
MPATH_INFO_METRIC |
|
|
|
|
MPATH_INFO_EXPTIME |
|
|
|
|
MPATH_INFO_DISCOVERY_TIMEOUT |
|
|
|
|
MPATH_INFO_DISCOVERY_RETRIES |
|
|
|
|
MPATH_INFO_FLAGS;
|
|
|
|
|
|
|
|
pinfo->frame_qlen = mpath->frame_queue.qlen;
|
2009-11-10 07:46:55 +08:00
|
|
|
pinfo->sn = mpath->sn;
|
2008-02-23 22:17:17 +08:00
|
|
|
pinfo->metric = mpath->metric;
|
|
|
|
if (time_before(jiffies, mpath->exp_time))
|
|
|
|
pinfo->exptime = jiffies_to_msecs(mpath->exp_time - jiffies);
|
|
|
|
pinfo->discovery_timeout =
|
|
|
|
jiffies_to_msecs(mpath->discovery_timeout);
|
|
|
|
pinfo->discovery_retries = mpath->discovery_retries;
|
|
|
|
pinfo->flags = 0;
|
|
|
|
if (mpath->flags & MESH_PATH_ACTIVE)
|
|
|
|
pinfo->flags |= NL80211_MPATH_FLAG_ACTIVE;
|
|
|
|
if (mpath->flags & MESH_PATH_RESOLVING)
|
|
|
|
pinfo->flags |= NL80211_MPATH_FLAG_RESOLVING;
|
2009-11-10 07:46:55 +08:00
|
|
|
if (mpath->flags & MESH_PATH_SN_VALID)
|
|
|
|
pinfo->flags |= NL80211_MPATH_FLAG_SN_VALID;
|
2008-02-23 22:17:17 +08:00
|
|
|
if (mpath->flags & MESH_PATH_FIXED)
|
|
|
|
pinfo->flags |= NL80211_MPATH_FLAG_FIXED;
|
|
|
|
if (mpath->flags & MESH_PATH_RESOLVING)
|
|
|
|
pinfo->flags |= NL80211_MPATH_FLAG_RESOLVING;
|
|
|
|
|
|
|
|
pinfo->flags = mpath->flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_get_mpath(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
u8 *dst, u8 *next_hop, struct mpath_info *pinfo)
|
|
|
|
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2008-02-23 22:17:17 +08:00
|
|
|
struct mesh_path *mpath;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
rcu_read_lock();
|
2008-08-03 08:04:37 +08:00
|
|
|
mpath = mesh_path_lookup(dst, sdata);
|
2008-02-23 22:17:17 +08:00
|
|
|
if (!mpath) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
memcpy(dst, mpath->dst, ETH_ALEN);
|
|
|
|
mpath_set_pinfo(mpath, next_hop, pinfo);
|
|
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_dump_mpath(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
int idx, u8 *dst, u8 *next_hop,
|
|
|
|
struct mpath_info *pinfo)
|
|
|
|
{
|
2008-07-29 19:22:52 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2008-02-23 22:17:17 +08:00
|
|
|
struct mesh_path *mpath;
|
|
|
|
|
2008-07-29 19:22:52 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
rcu_read_lock();
|
2008-08-03 08:04:37 +08:00
|
|
|
mpath = mesh_path_lookup_by_idx(idx, sdata);
|
2008-02-23 22:17:17 +08:00
|
|
|
if (!mpath) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
memcpy(dst, mpath->dst, ETH_ALEN);
|
|
|
|
mpath_set_pinfo(mpath, next_hop, pinfo);
|
|
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
|
|
}
|
2008-10-22 03:03:48 +08:00
|
|
|
|
|
|
|
static int ieee80211_get_mesh_params(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct mesh_config *conf)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
memcpy(conf, &(sdata->u.mesh.mshcfg), sizeof(struct mesh_config));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool _chg_mesh_attr(enum nl80211_meshconf_params parm, u32 mask)
|
|
|
|
{
|
|
|
|
return (mask >> (parm-1)) & 0x1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_set_mesh_params(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
const struct mesh_config *nconf, u32 mask)
|
|
|
|
{
|
|
|
|
struct mesh_config *conf;
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
2009-11-10 07:46:57 +08:00
|
|
|
struct ieee80211_if_mesh *ifmsh;
|
|
|
|
|
2008-10-22 03:03:48 +08:00
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
2009-11-10 07:46:57 +08:00
|
|
|
ifmsh = &sdata->u.mesh;
|
2008-10-22 03:03:48 +08:00
|
|
|
|
|
|
|
/* Set the config options which we are interested in setting */
|
|
|
|
conf = &(sdata->u.mesh.mshcfg);
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_RETRY_TIMEOUT, mask))
|
|
|
|
conf->dot11MeshRetryTimeout = nconf->dot11MeshRetryTimeout;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_CONFIRM_TIMEOUT, mask))
|
|
|
|
conf->dot11MeshConfirmTimeout = nconf->dot11MeshConfirmTimeout;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_HOLDING_TIMEOUT, mask))
|
|
|
|
conf->dot11MeshHoldingTimeout = nconf->dot11MeshHoldingTimeout;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_MAX_PEER_LINKS, mask))
|
|
|
|
conf->dot11MeshMaxPeerLinks = nconf->dot11MeshMaxPeerLinks;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_MAX_RETRIES, mask))
|
|
|
|
conf->dot11MeshMaxRetries = nconf->dot11MeshMaxRetries;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_TTL, mask))
|
|
|
|
conf->dot11MeshTTL = nconf->dot11MeshTTL;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_AUTO_OPEN_PLINKS, mask))
|
|
|
|
conf->auto_open_plinks = nconf->auto_open_plinks;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES, mask))
|
|
|
|
conf->dot11MeshHWMPmaxPREQretries =
|
|
|
|
nconf->dot11MeshHWMPmaxPREQretries;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_PATH_REFRESH_TIME, mask))
|
|
|
|
conf->path_refresh_time = nconf->path_refresh_time;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT, mask))
|
|
|
|
conf->min_discovery_timeout = nconf->min_discovery_timeout;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT, mask))
|
|
|
|
conf->dot11MeshHWMPactivePathTimeout =
|
|
|
|
nconf->dot11MeshHWMPactivePathTimeout;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL, mask))
|
|
|
|
conf->dot11MeshHWMPpreqMinInterval =
|
|
|
|
nconf->dot11MeshHWMPpreqMinInterval;
|
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME,
|
|
|
|
mask))
|
|
|
|
conf->dot11MeshHWMPnetDiameterTraversalTime =
|
|
|
|
nconf->dot11MeshHWMPnetDiameterTraversalTime;
|
2009-11-10 07:46:57 +08:00
|
|
|
if (_chg_mesh_attr(NL80211_MESHCONF_HWMP_ROOTMODE, mask)) {
|
|
|
|
conf->dot11MeshHWMPRootMode = nconf->dot11MeshHWMPRootMode;
|
|
|
|
ieee80211_mesh_root_setup(ifmsh);
|
|
|
|
}
|
2008-10-22 03:03:48 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-02-23 22:17:17 +08:00
|
|
|
#endif
|
|
|
|
|
2008-08-08 01:07:01 +08:00
|
|
|
static int ieee80211_change_bss(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct bss_parameters *params)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
|
|
|
u32 changed = 0;
|
|
|
|
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
if (params->use_cts_prot >= 0) {
|
2008-10-11 07:51:51 +08:00
|
|
|
sdata->vif.bss_conf.use_cts_prot = params->use_cts_prot;
|
2008-08-08 01:07:01 +08:00
|
|
|
changed |= BSS_CHANGED_ERP_CTS_PROT;
|
|
|
|
}
|
|
|
|
if (params->use_short_preamble >= 0) {
|
2008-10-11 07:51:51 +08:00
|
|
|
sdata->vif.bss_conf.use_short_preamble =
|
2008-08-08 01:07:01 +08:00
|
|
|
params->use_short_preamble;
|
|
|
|
changed |= BSS_CHANGED_ERP_PREAMBLE;
|
|
|
|
}
|
2010-01-15 10:00:48 +08:00
|
|
|
|
|
|
|
if (!sdata->vif.bss_conf.use_short_slot &&
|
|
|
|
sdata->local->hw.conf.channel->band == IEEE80211_BAND_5GHZ) {
|
|
|
|
sdata->vif.bss_conf.use_short_slot = true;
|
|
|
|
changed |= BSS_CHANGED_ERP_SLOT;
|
|
|
|
}
|
|
|
|
|
2008-08-08 01:07:01 +08:00
|
|
|
if (params->use_short_slot_time >= 0) {
|
2008-10-11 07:51:51 +08:00
|
|
|
sdata->vif.bss_conf.use_short_slot =
|
2008-08-08 01:07:01 +08:00
|
|
|
params->use_short_slot_time;
|
|
|
|
changed |= BSS_CHANGED_ERP_SLOT;
|
|
|
|
}
|
|
|
|
|
2008-10-30 22:59:22 +08:00
|
|
|
if (params->basic_rates) {
|
|
|
|
int i, j;
|
|
|
|
u32 rates = 0;
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
struct ieee80211_supported_band *sband =
|
|
|
|
wiphy->bands[local->oper_channel->band];
|
|
|
|
|
|
|
|
for (i = 0; i < params->basic_rates_len; i++) {
|
|
|
|
int rate = (params->basic_rates[i] & 0x7f) * 5;
|
|
|
|
for (j = 0; j < sband->n_bitrates; j++) {
|
|
|
|
if (sband->bitrates[j].bitrate == rate)
|
|
|
|
rates |= BIT(j);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sdata->vif.bss_conf.basic_rates = rates;
|
|
|
|
changed |= BSS_CHANGED_BASIC_RATES;
|
|
|
|
}
|
|
|
|
|
2010-04-27 07:23:36 +08:00
|
|
|
if (params->ap_isolate >= 0) {
|
|
|
|
if (params->ap_isolate)
|
|
|
|
sdata->flags |= IEEE80211_SDATA_DONT_BRIDGE_PACKETS;
|
|
|
|
else
|
|
|
|
sdata->flags &= ~IEEE80211_SDATA_DONT_BRIDGE_PACKETS;
|
|
|
|
}
|
|
|
|
|
2008-08-08 01:07:01 +08:00
|
|
|
ieee80211_bss_info_change_notify(sdata, changed);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-10-30 22:59:24 +08:00
|
|
|
static int ieee80211_set_txq_params(struct wiphy *wiphy,
|
|
|
|
struct ieee80211_txq_params *params)
|
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
struct ieee80211_tx_queue_params p;
|
|
|
|
|
|
|
|
if (!local->ops->conf_tx)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
|
|
p.aifs = params->aifs;
|
|
|
|
p.cw_max = params->cwmax;
|
|
|
|
p.cw_min = params->cwmin;
|
|
|
|
p.txop = params->txop;
|
2010-01-12 16:42:31 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Setting tx queue params disables u-apsd because it's only
|
|
|
|
* called in master mode.
|
|
|
|
*/
|
|
|
|
p.uapsd = false;
|
|
|
|
|
2009-04-24 00:52:52 +08:00
|
|
|
if (drv_conf_tx(local, params->queue, &p)) {
|
2008-10-30 22:59:24 +08:00
|
|
|
printk(KERN_DEBUG "%s: failed to set TX queue "
|
2009-06-09 22:18:32 +08:00
|
|
|
"parameters for queue %d\n",
|
|
|
|
wiphy_name(local->hw.wiphy), params->queue);
|
2008-10-30 22:59:24 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-26 22:15:24 +08:00
|
|
|
static int ieee80211_set_channel(struct wiphy *wiphy,
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
struct net_device *netdev,
|
2008-11-26 22:15:24 +08:00
|
|
|
struct ieee80211_channel *chan,
|
2008-12-12 14:27:43 +08:00
|
|
|
enum nl80211_channel_type channel_type)
|
2008-11-26 22:15:24 +08:00
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
2010-05-05 21:28:27 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = NULL;
|
|
|
|
|
|
|
|
if (netdev)
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(netdev);
|
2008-11-26 22:15:24 +08:00
|
|
|
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
switch (ieee80211_get_channel_mode(local, NULL)) {
|
|
|
|
case CHAN_MODE_HOPPING:
|
|
|
|
return -EBUSY;
|
|
|
|
case CHAN_MODE_FIXED:
|
2010-05-05 21:28:27 +08:00
|
|
|
if (local->oper_channel != chan)
|
|
|
|
return -EBUSY;
|
|
|
|
if (!sdata && local->_oper_channel_type == channel_type)
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
return 0;
|
2010-05-05 21:28:27 +08:00
|
|
|
break;
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
case CHAN_MODE_UNDEFINED:
|
|
|
|
break;
|
|
|
|
}
|
2008-11-26 22:15:24 +08:00
|
|
|
|
|
|
|
local->oper_channel = chan;
|
|
|
|
|
2010-05-05 21:28:27 +08:00
|
|
|
if (!ieee80211_set_channel_type(local, sdata, channel_type))
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
|
|
|
|
if (sdata && sdata->vif.type != NL80211_IFTYPE_MONITOR)
|
|
|
|
ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_HT);
|
|
|
|
|
|
|
|
return 0;
|
2008-11-26 22:15:24 +08:00
|
|
|
}
|
|
|
|
|
2009-01-20 00:20:53 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int ieee80211_suspend(struct wiphy *wiphy)
|
|
|
|
{
|
|
|
|
return __ieee80211_suspend(wiphy_priv(wiphy));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_resume(struct wiphy *wiphy)
|
|
|
|
{
|
|
|
|
return __ieee80211_resume(wiphy_priv(wiphy));
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define ieee80211_suspend NULL
|
|
|
|
#define ieee80211_resume NULL
|
|
|
|
#endif
|
|
|
|
|
2009-02-11 04:25:55 +08:00
|
|
|
static int ieee80211_scan(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct cfg80211_scan_request *req)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata;
|
|
|
|
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_STATION &&
|
|
|
|
sdata->vif.type != NL80211_IFTYPE_ADHOC &&
|
2009-04-16 23:44:53 +08:00
|
|
|
sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
|
|
|
|
(sdata->vif.type != NL80211_IFTYPE_AP || sdata->u.ap.beacon))
|
2009-02-11 04:25:55 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
return ieee80211_request_scan(sdata, req);
|
|
|
|
}
|
|
|
|
|
2009-03-19 19:39:22 +08:00
|
|
|
static int ieee80211_auth(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
struct cfg80211_auth_request *req)
|
|
|
|
{
|
2009-07-07 09:45:17 +08:00
|
|
|
return ieee80211_mgd_auth(IEEE80211_DEV_TO_SUB_IF(dev), req);
|
2009-03-19 19:39:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_assoc(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
struct cfg80211_assoc_request *req)
|
|
|
|
{
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
switch (ieee80211_get_channel_mode(local, sdata)) {
|
|
|
|
case CHAN_MODE_HOPPING:
|
|
|
|
return -EBUSY;
|
|
|
|
case CHAN_MODE_FIXED:
|
|
|
|
if (local->oper_channel == req->bss->channel)
|
|
|
|
break;
|
|
|
|
return -EBUSY;
|
|
|
|
case CHAN_MODE_UNDEFINED:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-07-07 09:45:17 +08:00
|
|
|
return ieee80211_mgd_assoc(IEEE80211_DEV_TO_SUB_IF(dev), req);
|
2009-03-19 19:39:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_deauth(struct wiphy *wiphy, struct net_device *dev,
|
2009-07-07 09:56:11 +08:00
|
|
|
struct cfg80211_deauth_request *req,
|
|
|
|
void *cookie)
|
2009-03-19 19:39:22 +08:00
|
|
|
{
|
2009-07-07 09:56:11 +08:00
|
|
|
return ieee80211_mgd_deauth(IEEE80211_DEV_TO_SUB_IF(dev),
|
|
|
|
req, cookie);
|
2009-03-19 19:39:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_disassoc(struct wiphy *wiphy, struct net_device *dev,
|
2009-07-07 09:56:11 +08:00
|
|
|
struct cfg80211_disassoc_request *req,
|
|
|
|
void *cookie)
|
2009-03-19 19:39:22 +08:00
|
|
|
{
|
2009-07-07 09:56:11 +08:00
|
|
|
return ieee80211_mgd_disassoc(IEEE80211_DEV_TO_SUB_IF(dev),
|
|
|
|
req, cookie);
|
2009-03-19 19:39:22 +08:00
|
|
|
}
|
|
|
|
|
2009-04-20 03:25:43 +08:00
|
|
|
static int ieee80211_join_ibss(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
struct cfg80211_ibss_params *params)
|
|
|
|
{
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
2009-04-20 03:25:43 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
cfg80211/mac80211: better channel handling
Currently (all tested with hwsim) you can do stupid
things like setting up an AP on a certain channel,
then adding another virtual interface and making
that associate on another channel -- this will make
the beaconing to move channel but obviously without
the necessary IEs data update.
In order to improve this situation, first make the
configuration APIs (cfg80211 and nl80211) aware of
multi-channel operation -- we'll eventually need
that in the future anyway. There's one userland API
change and one API addition. The API change is that
now SET_WIPHY must be called with virtual interface
index rather than only wiphy index in order to take
effect for that interface -- luckily all current
users (hostapd) do that. For monitor interfaces, the
old setting is preserved, but monitors are always
slaved to other devices anyway so no guarantees.
The second userland API change is the introduction
of a per virtual interface SET_CHANNEL command, that
hostapd should use going forward to make it easier
to understand what's going on (it can automatically
detect a kernel with this command).
Other than mac80211, no existing cfg80211 drivers
are affected by this change because they only allow
a single virtual interface.
mac80211, however, now needs to be aware that the
channel settings are per interface now, and needs
to disallow (for now) real multi-channel operation,
which is another important part of this patch.
One of the immediate benefits is that you can now
start hostapd to operate on a hardware that already
has a connection on another virtual interface, as
long as you specify the same channel.
Note that two things are left unhandled (this is an
improvement -- not a complete fix):
* different HT/no-HT modes
currently you could start an HT AP and then
connect to a non-HT network on the same channel
which would configure the hardware for no HT;
that can be fixed fairly easily
* CSA
An AP we're connected to on a virtual interface
might indicate switching channels, and in that
case we would follow it, regardless of how many
other interfaces are operating; this requires
more effort to fix but is pretty rare after all
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-05-05 21:25:02 +08:00
|
|
|
switch (ieee80211_get_channel_mode(local, sdata)) {
|
|
|
|
case CHAN_MODE_HOPPING:
|
|
|
|
return -EBUSY;
|
|
|
|
case CHAN_MODE_FIXED:
|
|
|
|
if (!params->channel_fixed)
|
|
|
|
return -EBUSY;
|
|
|
|
if (local->oper_channel == params->channel)
|
|
|
|
break;
|
|
|
|
return -EBUSY;
|
|
|
|
case CHAN_MODE_UNDEFINED:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-04-20 03:25:43 +08:00
|
|
|
return ieee80211_ibss_join(sdata, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
return ieee80211_ibss_leave(sdata);
|
|
|
|
}
|
|
|
|
|
2009-04-21 00:39:05 +08:00
|
|
|
static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
|
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
2009-04-24 00:52:52 +08:00
|
|
|
int err;
|
2009-04-21 00:39:05 +08:00
|
|
|
|
2009-12-22 05:50:48 +08:00
|
|
|
if (changed & WIPHY_PARAM_COVERAGE_CLASS) {
|
|
|
|
err = drv_set_coverage_class(local, wiphy->coverage_class);
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-04-21 00:39:05 +08:00
|
|
|
if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
|
2009-04-24 00:52:52 +08:00
|
|
|
err = drv_set_rts_threshold(local, wiphy->rts_threshold);
|
2009-04-21 00:39:05 +08:00
|
|
|
|
2009-04-24 00:52:52 +08:00
|
|
|
if (err)
|
|
|
|
return err;
|
2009-04-21 00:39:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (changed & WIPHY_PARAM_RETRY_SHORT)
|
|
|
|
local->hw.conf.short_frame_max_tx_count = wiphy->retry_short;
|
|
|
|
if (changed & WIPHY_PARAM_RETRY_LONG)
|
|
|
|
local->hw.conf.long_frame_max_tx_count = wiphy->retry_long;
|
|
|
|
if (changed &
|
|
|
|
(WIPHY_PARAM_RETRY_SHORT | WIPHY_PARAM_RETRY_LONG))
|
|
|
|
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_RETRY_LIMITS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:39 +08:00
|
|
|
static int ieee80211_set_tx_power(struct wiphy *wiphy,
|
2010-06-23 17:12:37 +08:00
|
|
|
enum nl80211_tx_power_setting type, int mbm)
|
2009-06-02 19:01:39 +08:00
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
struct ieee80211_channel *chan = local->hw.conf.channel;
|
|
|
|
u32 changes = 0;
|
|
|
|
|
|
|
|
switch (type) {
|
2010-06-23 17:12:37 +08:00
|
|
|
case NL80211_TX_POWER_AUTOMATIC:
|
2009-06-02 19:01:39 +08:00
|
|
|
local->user_power_level = -1;
|
|
|
|
break;
|
2010-06-23 17:12:37 +08:00
|
|
|
case NL80211_TX_POWER_LIMITED:
|
|
|
|
if (mbm < 0 || (mbm % 100))
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
local->user_power_level = MBM_TO_DBM(mbm);
|
2009-06-02 19:01:39 +08:00
|
|
|
break;
|
2010-06-23 17:12:37 +08:00
|
|
|
case NL80211_TX_POWER_FIXED:
|
|
|
|
if (mbm < 0 || (mbm % 100))
|
|
|
|
return -EOPNOTSUPP;
|
2009-06-02 19:01:39 +08:00
|
|
|
/* TODO: move to cfg80211 when it knows the channel */
|
2010-06-23 17:12:37 +08:00
|
|
|
if (MBM_TO_DBM(mbm) > chan->max_power)
|
2009-06-02 19:01:39 +08:00
|
|
|
return -EINVAL;
|
2010-06-23 17:12:37 +08:00
|
|
|
local->user_power_level = MBM_TO_DBM(mbm);
|
2009-06-02 19:01:39 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ieee80211_hw_config(local, changes);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_get_tx_power(struct wiphy *wiphy, int *dbm)
|
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
|
|
|
|
*dbm = local->hw.conf.power_level;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-07-02 03:26:58 +08:00
|
|
|
static int ieee80211_set_wds_peer(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
u8 *addr)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
memcpy(&sdata->u.wds.remote_addr, addr, ETH_ALEN);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:41 +08:00
|
|
|
static void ieee80211_rfkill_poll(struct wiphy *wiphy)
|
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
|
|
|
|
drv_rfkill_poll(local);
|
|
|
|
}
|
|
|
|
|
2009-07-02 03:26:51 +08:00
|
|
|
#ifdef CONFIG_NL80211_TESTMODE
|
2009-07-07 09:54:43 +08:00
|
|
|
static int ieee80211_testmode_cmd(struct wiphy *wiphy, void *data, int len)
|
2009-07-02 03:26:51 +08:00
|
|
|
{
|
|
|
|
struct ieee80211_local *local = wiphy_priv(wiphy);
|
|
|
|
|
|
|
|
if (!local->ops->testmode_cmd)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
return local->ops->testmode_cmd(&local->hw, data, len);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-12-01 20:37:02 +08:00
|
|
|
int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata,
|
|
|
|
enum ieee80211_smps_mode smps_mode)
|
|
|
|
{
|
|
|
|
const u8 *ap;
|
|
|
|
enum ieee80211_smps_mode old_req;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
old_req = sdata->u.mgd.req_smps;
|
|
|
|
sdata->u.mgd.req_smps = smps_mode;
|
|
|
|
|
|
|
|
if (old_req == smps_mode &&
|
|
|
|
smps_mode != IEEE80211_SMPS_AUTOMATIC)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If not associated, or current association is not an HT
|
|
|
|
* association, there's no need to send an action frame.
|
|
|
|
*/
|
|
|
|
if (!sdata->u.mgd.associated ||
|
2010-05-05 21:28:27 +08:00
|
|
|
sdata->vif.bss_conf.channel_type == NL80211_CHAN_NO_HT) {
|
2009-12-01 20:37:02 +08:00
|
|
|
mutex_lock(&sdata->local->iflist_mtx);
|
|
|
|
ieee80211_recalc_smps(sdata->local, sdata);
|
|
|
|
mutex_unlock(&sdata->local->iflist_mtx);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-23 20:15:39 +08:00
|
|
|
ap = sdata->u.mgd.associated->bssid;
|
2009-12-01 20:37:02 +08:00
|
|
|
|
|
|
|
if (smps_mode == IEEE80211_SMPS_AUTOMATIC) {
|
|
|
|
if (sdata->u.mgd.powersave)
|
|
|
|
smps_mode = IEEE80211_SMPS_DYNAMIC;
|
|
|
|
else
|
|
|
|
smps_mode = IEEE80211_SMPS_OFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* send SM PS frame to AP */
|
|
|
|
err = ieee80211_send_smps_action(sdata, smps_mode,
|
|
|
|
ap, ap);
|
|
|
|
if (err)
|
|
|
|
sdata->u.mgd.req_smps = old_req;
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-07-02 03:26:57 +08:00
|
|
|
static int ieee80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
bool enabled, int timeout)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
|
|
|
|
|
2010-01-15 19:21:37 +08:00
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_STATION)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2009-07-02 03:26:57 +08:00
|
|
|
if (!(local->hw.flags & IEEE80211_HW_SUPPORTS_PS))
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
if (enabled == sdata->u.mgd.powersave &&
|
2010-06-09 14:51:52 +08:00
|
|
|
timeout == local->dynamic_ps_forced_timeout)
|
2009-07-02 03:26:57 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
sdata->u.mgd.powersave = enabled;
|
2010-06-09 14:51:52 +08:00
|
|
|
local->dynamic_ps_forced_timeout = timeout;
|
2009-07-02 03:26:57 +08:00
|
|
|
|
2009-12-01 20:37:02 +08:00
|
|
|
/* no change, but if automatic follow powersave */
|
|
|
|
mutex_lock(&sdata->u.mgd.mtx);
|
|
|
|
__ieee80211_request_smps(sdata, sdata->u.mgd.req_smps);
|
|
|
|
mutex_unlock(&sdata->u.mgd.mtx);
|
|
|
|
|
2009-07-02 03:26:57 +08:00
|
|
|
if (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS)
|
|
|
|
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
|
|
|
|
|
|
|
|
ieee80211_recalc_ps(local, -1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-23 15:02:34 +08:00
|
|
|
static int ieee80211_set_cqm_rssi_config(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
s32 rssi_thold, u32 rssi_hyst)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
|
|
|
|
struct ieee80211_vif *vif = &sdata->vif;
|
|
|
|
struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
|
|
|
|
|
|
|
|
if (rssi_thold == bss_conf->cqm_rssi_thold &&
|
|
|
|
rssi_hyst == bss_conf->cqm_rssi_hyst)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bss_conf->cqm_rssi_thold = rssi_thold;
|
|
|
|
bss_conf->cqm_rssi_hyst = rssi_hyst;
|
|
|
|
|
2010-03-30 14:28:30 +08:00
|
|
|
if (!(local->hw.flags & IEEE80211_HW_SUPPORTS_CQM_RSSI)) {
|
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_STATION)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-23 15:02:34 +08:00
|
|
|
/* tell the driver upon association, unless already associated */
|
|
|
|
if (sdata->u.mgd.associated)
|
|
|
|
ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_CQM);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-07-02 03:26:59 +08:00
|
|
|
static int ieee80211_set_bitrate_mask(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
const u8 *addr,
|
|
|
|
const struct cfg80211_bitrate_mask *mask)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
|
2009-12-04 16:26:38 +08:00
|
|
|
int i;
|
2009-07-02 03:26:59 +08:00
|
|
|
|
2009-12-04 16:26:38 +08:00
|
|
|
/*
|
|
|
|
* This _could_ be supported by providing a hook for
|
|
|
|
* drivers for this function, but at this point it
|
|
|
|
* doesn't seem worth bothering.
|
|
|
|
*/
|
|
|
|
if (local->hw.flags & IEEE80211_HW_HAS_RATE_CONTROL)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2009-07-02 03:26:59 +08:00
|
|
|
|
2010-01-06 19:09:08 +08:00
|
|
|
for (i = 0; i < IEEE80211_NUM_BANDS; i++)
|
|
|
|
sdata->rc_rateidx_mask[i] = mask->control[i].legacy;
|
2009-07-02 03:26:59 +08:00
|
|
|
|
2010-01-06 19:09:08 +08:00
|
|
|
return 0;
|
2009-07-02 03:26:59 +08:00
|
|
|
}
|
|
|
|
|
2009-12-23 20:15:42 +08:00
|
|
|
static int ieee80211_remain_on_channel(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct ieee80211_channel *chan,
|
|
|
|
enum nl80211_channel_type channel_type,
|
|
|
|
unsigned int duration,
|
|
|
|
u64 *cookie)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
return ieee80211_wk_remain_on_channel(sdata, chan, channel_type,
|
|
|
|
duration, cookie);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
|
|
|
struct net_device *dev,
|
|
|
|
u64 cookie)
|
|
|
|
{
|
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
|
|
|
|
return ieee80211_wk_cancel_remain_on_channel(sdata, cookie);
|
|
|
|
}
|
|
|
|
|
2010-08-12 21:38:38 +08:00
|
|
|
static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
struct ieee80211_channel *chan,
|
|
|
|
enum nl80211_channel_type channel_type,
|
|
|
|
bool channel_type_valid,
|
|
|
|
const u8 *buf, size_t len, u64 *cookie)
|
2010-02-15 18:53:10 +08:00
|
|
|
{
|
2010-06-09 23:20:33 +08:00
|
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct sta_info *sta;
|
|
|
|
const struct ieee80211_mgmt *mgmt = (void *)buf;
|
|
|
|
u32 flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX |
|
|
|
|
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
|
|
|
|
|
|
/* Check that we are on the requested channel for transmission */
|
|
|
|
if (chan != local->tmp_channel &&
|
|
|
|
chan != local->oper_channel)
|
|
|
|
return -EBUSY;
|
|
|
|
if (channel_type_valid &&
|
|
|
|
(channel_type != local->tmp_channel_type &&
|
|
|
|
channel_type != local->_oper_channel_type))
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
switch (sdata->vif.type) {
|
|
|
|
case NL80211_IFTYPE_ADHOC:
|
|
|
|
if (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)
|
|
|
|
break;
|
|
|
|
rcu_read_lock();
|
|
|
|
sta = sta_info_get(sdata, mgmt->da);
|
|
|
|
rcu_read_unlock();
|
|
|
|
if (!sta)
|
|
|
|
return -ENOLINK;
|
|
|
|
break;
|
|
|
|
case NL80211_IFTYPE_STATION:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
|
|
|
|
if (!skb)
|
|
|
|
return -ENOMEM;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
|
|
|
|
|
|
memcpy(skb_put(skb, len), buf, len);
|
|
|
|
|
|
|
|
IEEE80211_SKB_CB(skb)->flags = flags;
|
|
|
|
|
|
|
|
skb->dev = sdata->dev;
|
|
|
|
ieee80211_tx_skb(sdata, skb);
|
|
|
|
|
|
|
|
*cookie = (unsigned long) skb;
|
|
|
|
return 0;
|
2010-02-15 18:53:10 +08:00
|
|
|
}
|
|
|
|
|
2007-05-06 02:45:53 +08:00
|
|
|
struct cfg80211_ops mac80211_config_ops = {
|
|
|
|
.add_virtual_intf = ieee80211_add_iface,
|
|
|
|
.del_virtual_intf = ieee80211_del_iface,
|
2007-09-29 03:52:27 +08:00
|
|
|
.change_virtual_intf = ieee80211_change_iface,
|
2007-12-19 09:03:30 +08:00
|
|
|
.add_key = ieee80211_add_key,
|
|
|
|
.del_key = ieee80211_del_key,
|
2007-12-19 09:03:31 +08:00
|
|
|
.get_key = ieee80211_get_key,
|
2007-12-19 09:03:30 +08:00
|
|
|
.set_default_key = ieee80211_config_default_key,
|
2009-01-08 19:32:02 +08:00
|
|
|
.set_default_mgmt_key = ieee80211_config_default_mgmt_key,
|
2007-12-19 09:03:33 +08:00
|
|
|
.add_beacon = ieee80211_add_beacon,
|
|
|
|
.set_beacon = ieee80211_set_beacon,
|
|
|
|
.del_beacon = ieee80211_del_beacon,
|
2007-12-19 09:03:35 +08:00
|
|
|
.add_station = ieee80211_add_station,
|
|
|
|
.del_station = ieee80211_del_station,
|
|
|
|
.change_station = ieee80211_change_station,
|
2007-12-19 09:03:37 +08:00
|
|
|
.get_station = ieee80211_get_station,
|
2008-02-23 22:17:17 +08:00
|
|
|
.dump_station = ieee80211_dump_station,
|
2010-04-19 16:23:57 +08:00
|
|
|
.dump_survey = ieee80211_dump_survey,
|
2008-02-23 22:17:17 +08:00
|
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
|
|
.add_mpath = ieee80211_add_mpath,
|
|
|
|
.del_mpath = ieee80211_del_mpath,
|
|
|
|
.change_mpath = ieee80211_change_mpath,
|
|
|
|
.get_mpath = ieee80211_get_mpath,
|
|
|
|
.dump_mpath = ieee80211_dump_mpath,
|
2008-10-22 03:03:48 +08:00
|
|
|
.set_mesh_params = ieee80211_set_mesh_params,
|
|
|
|
.get_mesh_params = ieee80211_get_mesh_params,
|
2008-02-23 22:17:17 +08:00
|
|
|
#endif
|
2008-08-08 01:07:01 +08:00
|
|
|
.change_bss = ieee80211_change_bss,
|
2008-10-30 22:59:24 +08:00
|
|
|
.set_txq_params = ieee80211_set_txq_params,
|
2008-11-26 22:15:24 +08:00
|
|
|
.set_channel = ieee80211_set_channel,
|
2009-01-20 00:20:53 +08:00
|
|
|
.suspend = ieee80211_suspend,
|
|
|
|
.resume = ieee80211_resume,
|
2009-02-11 04:25:55 +08:00
|
|
|
.scan = ieee80211_scan,
|
2009-03-19 19:39:22 +08:00
|
|
|
.auth = ieee80211_auth,
|
|
|
|
.assoc = ieee80211_assoc,
|
|
|
|
.deauth = ieee80211_deauth,
|
|
|
|
.disassoc = ieee80211_disassoc,
|
2009-04-20 03:25:43 +08:00
|
|
|
.join_ibss = ieee80211_join_ibss,
|
|
|
|
.leave_ibss = ieee80211_leave_ibss,
|
2009-04-21 00:39:05 +08:00
|
|
|
.set_wiphy_params = ieee80211_set_wiphy_params,
|
2009-06-02 19:01:39 +08:00
|
|
|
.set_tx_power = ieee80211_set_tx_power,
|
|
|
|
.get_tx_power = ieee80211_get_tx_power,
|
2009-07-02 03:26:58 +08:00
|
|
|
.set_wds_peer = ieee80211_set_wds_peer,
|
2009-06-02 19:01:41 +08:00
|
|
|
.rfkill_poll = ieee80211_rfkill_poll,
|
2009-07-02 03:26:51 +08:00
|
|
|
CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
|
2009-07-02 03:26:57 +08:00
|
|
|
.set_power_mgmt = ieee80211_set_power_mgmt,
|
2009-07-02 03:26:59 +08:00
|
|
|
.set_bitrate_mask = ieee80211_set_bitrate_mask,
|
2009-12-23 20:15:42 +08:00
|
|
|
.remain_on_channel = ieee80211_remain_on_channel,
|
|
|
|
.cancel_remain_on_channel = ieee80211_cancel_remain_on_channel,
|
2010-08-12 21:38:38 +08:00
|
|
|
.mgmt_tx = ieee80211_mgmt_tx,
|
2010-03-23 15:02:34 +08:00
|
|
|
.set_cqm_rssi_config = ieee80211_set_cqm_rssi_config,
|
2007-05-06 02:45:53 +08:00
|
|
|
};
|