Skip to content
Snippets Groups Projects
rndis_wlan.c 75.9 KiB
Newer Older
/*
 * Driver for RNDIS based wireless USB devices.
 *
 * Copyright (C) 2007 by Bjorge Dijkstra <bjd@jooz.net>
 * Copyright (C) 2008-2009 by Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Portions of this file are based on NDISwrapper project,
 *  Copyright (C) 2003-2005 Pontus Fuchs, Giridhar Pemmasani
 *  http://ndiswrapper.sourceforge.net/
 */

// #define	DEBUG			// error path messages, extra info
// #define	VERBOSE			// more; success messages

#include <linux/module.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/wireless.h>
Johannes Berg's avatar
Johannes Berg committed
#include <linux/ieee80211.h>
#include <linux/if_arp.h>
#include <linux/ctype.h>
#include <linux/spinlock.h>
#include <net/iw_handler.h>
#include <net/cfg80211.h>
#include <linux/usb/usbnet.h>
#include <linux/usb/rndis_host.h>


/* NOTE: All these are settings for Broadcom chipset */
static char modparam_country[4] = "EU";
module_param_string(country, modparam_country, 4, 0444);
MODULE_PARM_DESC(country, "Country code (ISO 3166-1 alpha-2), default: EU");

static int modparam_frameburst = 1;
module_param_named(frameburst, modparam_frameburst, int, 0444);
MODULE_PARM_DESC(frameburst, "enable frame bursting (default: on)");

static int modparam_afterburner = 0;
module_param_named(afterburner, modparam_afterburner, int, 0444);
MODULE_PARM_DESC(afterburner,
	"enable afterburner aka '125 High Speed Mode' (default: off)");

static int modparam_power_save = 0;
module_param_named(power_save, modparam_power_save, int, 0444);
MODULE_PARM_DESC(power_save,
	"set power save mode: 0=off, 1=on, 2=fast (default: off)");

static int modparam_power_output = 3;
module_param_named(power_output, modparam_power_output, int, 0444);
MODULE_PARM_DESC(power_output,
	"set power output: 0=25%, 1=50%, 2=75%, 3=100% (default: 100%)");

static int modparam_roamtrigger = -70;
module_param_named(roamtrigger, modparam_roamtrigger, int, 0444);
MODULE_PARM_DESC(roamtrigger,
	"set roaming dBm trigger: -80=optimize for distance, "
				"-60=bandwidth (default: -70)");

static int modparam_roamdelta = 1;
module_param_named(roamdelta, modparam_roamdelta, int, 0444);
MODULE_PARM_DESC(roamdelta,
	"set roaming tendency: 0=aggressive, 1=moderate, "
				"2=conservative (default: moderate)");

static int modparam_workaround_interval = 500;
module_param_named(workaround_interval, modparam_workaround_interval,
							int, 0444);
MODULE_PARM_DESC(workaround_interval,
	"set stall workaround interval in msecs (default: 500)");


/* various RNDIS OID defs */
#define OID_GEN_LINK_SPEED			cpu_to_le32(0x00010107)
#define OID_GEN_RNDIS_CONFIG_PARAMETER		cpu_to_le32(0x0001021b)

#define OID_GEN_XMIT_OK				cpu_to_le32(0x00020101)
#define OID_GEN_RCV_OK				cpu_to_le32(0x00020102)
#define OID_GEN_XMIT_ERROR			cpu_to_le32(0x00020103)
#define OID_GEN_RCV_ERROR			cpu_to_le32(0x00020104)
#define OID_GEN_RCV_NO_BUFFER			cpu_to_le32(0x00020105)

#define OID_802_3_CURRENT_ADDRESS		cpu_to_le32(0x01010102)
#define OID_802_3_MULTICAST_LIST		cpu_to_le32(0x01010103)
#define OID_802_3_MAXIMUM_LIST_SIZE		cpu_to_le32(0x01010104)

#define OID_802_11_BSSID			cpu_to_le32(0x0d010101)
#define OID_802_11_SSID				cpu_to_le32(0x0d010102)
#define OID_802_11_INFRASTRUCTURE_MODE		cpu_to_le32(0x0d010108)
#define OID_802_11_ADD_WEP			cpu_to_le32(0x0d010113)
#define OID_802_11_REMOVE_WEP			cpu_to_le32(0x0d010114)
#define OID_802_11_DISASSOCIATE			cpu_to_le32(0x0d010115)
#define OID_802_11_AUTHENTICATION_MODE		cpu_to_le32(0x0d010118)
#define OID_802_11_PRIVACY_FILTER		cpu_to_le32(0x0d010119)
#define OID_802_11_BSSID_LIST_SCAN		cpu_to_le32(0x0d01011a)
#define OID_802_11_ENCRYPTION_STATUS		cpu_to_le32(0x0d01011b)
#define OID_802_11_ADD_KEY			cpu_to_le32(0x0d01011d)
#define OID_802_11_REMOVE_KEY			cpu_to_le32(0x0d01011e)
#define OID_802_11_ASSOCIATION_INFORMATION	cpu_to_le32(0x0d01011f)
#define OID_802_11_PMKID			cpu_to_le32(0x0d010123)
#define OID_802_11_NETWORK_TYPES_SUPPORTED	cpu_to_le32(0x0d010203)
#define OID_802_11_NETWORK_TYPE_IN_USE		cpu_to_le32(0x0d010204)
#define OID_802_11_TX_POWER_LEVEL		cpu_to_le32(0x0d010205)
#define OID_802_11_RSSI				cpu_to_le32(0x0d010206)
#define OID_802_11_RSSI_TRIGGER			cpu_to_le32(0x0d010207)
#define OID_802_11_FRAGMENTATION_THRESHOLD	cpu_to_le32(0x0d010209)
#define OID_802_11_RTS_THRESHOLD		cpu_to_le32(0x0d01020a)
#define OID_802_11_SUPPORTED_RATES		cpu_to_le32(0x0d01020e)
#define OID_802_11_CONFIGURATION		cpu_to_le32(0x0d010211)
#define OID_802_11_BSSID_LIST			cpu_to_le32(0x0d010217)


/* Typical noise/maximum signal level values taken from ndiswrapper iw_ndis.h */
#define	WL_NOISE	-96	/* typical noise level in dBm */
#define	WL_SIGMAX	-32	/* typical maximum signal level in dBm */


/* Assume that Broadcom 4320 (only chipset at time of writing known to be
 * based on wireless rndis) has default txpower of 13dBm.
 * This value is from Linksys WUSB54GSC User Guide, Appendix F: Specifications.
 *  100% : 20 mW ~ 13dBm
 *   75% : 15 mW ~ 12dBm
 *   50% : 10 mW ~ 10dBm
 *   25% :  5 mW ~  7dBm
#define BCM4320_DEFAULT_TXPOWER_DBM_100 13
#define BCM4320_DEFAULT_TXPOWER_DBM_75  12
#define BCM4320_DEFAULT_TXPOWER_DBM_50  10
#define BCM4320_DEFAULT_TXPOWER_DBM_25  7


/* codes for "status" field of completion messages */
#define RNDIS_STATUS_ADAPTER_NOT_READY		cpu_to_le32(0xc0010011)
#define RNDIS_STATUS_ADAPTER_NOT_OPEN		cpu_to_le32(0xc0010012)


/* NDIS data structures. Taken from wpa_supplicant driver_ndis.c
 * slightly modified for datatype endianess, etc
 */
#define NDIS_802_11_LENGTH_SSID 32
#define NDIS_802_11_LENGTH_RATES 8
#define NDIS_802_11_LENGTH_RATES_EX 16

	NDIS_80211_TYPE_FREQ_HOP,
	NDIS_80211_TYPE_DIRECT_SEQ,
	NDIS_80211_TYPE_OFDM_A,
	NDIS_80211_TYPE_OFDM_G
	NDIS_80211_INFRA_ADHOC,
	NDIS_80211_INFRA_INFRA,
	NDIS_80211_INFRA_AUTO_UNKNOWN
	NDIS_80211_AUTH_OPEN,
	NDIS_80211_AUTH_SHARED,
	NDIS_80211_AUTH_AUTO_SWITCH,
	NDIS_80211_AUTH_WPA,
	NDIS_80211_AUTH_WPA_PSK,
	NDIS_80211_AUTH_WPA_NONE,
	NDIS_80211_AUTH_WPA2,
	NDIS_80211_AUTH_WPA2_PSK
	NDIS_80211_ENCR_WEP_ENABLED,
	NDIS_80211_ENCR_DISABLED,
	NDIS_80211_ENCR_WEP_KEY_ABSENT,
	NDIS_80211_ENCR_NOT_SUPPORTED,
	NDIS_80211_ENCR_TKIP_ENABLED,
	NDIS_80211_ENCR_TKIP_KEY_ABSENT,
	NDIS_80211_ENCR_CCMP_ENABLED,
	NDIS_80211_ENCR_CCMP_KEY_ABSENT
	NDIS_80211_PRIV_ACCEPT_ALL,
	NDIS_80211_PRIV_8021X_WEP
enum ndis_80211_addkey_bits {
	NDIS_80211_ADDKEY_8021X_AUTH = cpu_to_le32(1 << 28),
	NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ = cpu_to_le32(1 << 29),
	NDIS_80211_ADDKEY_PAIRWISE_KEY = cpu_to_le32(1 << 30),
	NDIS_80211_ADDKEY_TRANSMIT_KEY = cpu_to_le32(1 << 31)
};

enum ndis_80211_addwep_bits {
	NDIS_80211_ADDWEP_PERCLIENT_KEY = cpu_to_le32(1 << 30),
	NDIS_80211_ADDWEP_TRANSMIT_KEY = cpu_to_le32(1 << 31)
	__le32 length;
	u8 essid[NDIS_802_11_LENGTH_SSID];
struct ndis_80211_conf_freq_hop {
	__le32 length;
	__le32 hop_pattern;
	__le32 hop_set;
	__le32 dwell_time;
	__le32 length;
	__le32 beacon_period;
	__le32 atim_window;
	__le32 ds_config;
	struct ndis_80211_conf_freq_hop fh_config;
struct ndis_80211_bssid_ex {
	__le32 length;
	u8 mac[6];
	u8 padding[2];
	struct ndis_80211_ssid ssid;
	__le32 privacy;
	__le32 rssi;
	__le32 net_type;
	struct ndis_80211_conf config;
	__le32 net_infra;
	u8 rates[NDIS_802_11_LENGTH_RATES_EX];
	__le32 ie_length;
	u8 ies[0];
struct ndis_80211_bssid_list_ex {
	__le32 num_items;
	struct ndis_80211_bssid_ex bssid[0];
struct ndis_80211_fixed_ies {
	u8 timestamp[8];
	__le16 beacon_interval;
	__le16 capabilities;
	__le32 size;
	__le32 index;
	__le32 length;
	u8 material[32];
	__le32 size;
	__le32 index;
	__le32 length;
	u8 bssid[6];
	u8 padding[6];
	u8 rsc[8];
	u8 material[32];
struct ndis_80211_remove_key {
	__le32 size;
	__le32 index;
	u8 bssid[6];
	__le32 name_offs;
	__le32 name_length;
	__le32 type;
	__le32 value_offs;
	__le32 value_length;
struct ndis_80211_assoc_info {
	__le32 length;
	__le16 req_ies;
	struct req_ie {
		__le16 capa;
		__le16 listen_interval;
		u8 cur_ap_address[6];
	} req_ie;
	__le32 req_ie_length;
	__le32 offset_req_ies;
	__le16 resp_ies;
	struct resp_ie {
		__le16 capa;
		__le16 status_code;
		__le16 assoc_id;
	} resp_ie;
	__le32 resp_ie_length;
	__le32 offset_resp_ies;
} __attribute__((packed));

/* these have to match what is in wpa_supplicant */
enum wpa_alg { WPA_ALG_NONE, WPA_ALG_WEP, WPA_ALG_TKIP, WPA_ALG_CCMP };
enum wpa_cipher { CIPHER_NONE, CIPHER_WEP40, CIPHER_TKIP, CIPHER_CCMP,
		  CIPHER_WEP104 };
enum wpa_key_mgmt { KEY_MGMT_802_1X, KEY_MGMT_PSK, KEY_MGMT_NONE,
		    KEY_MGMT_802_1X_NO_WPA, KEY_MGMT_WPA_NONE };

/*
 *  private data
 */
#define NET_TYPE_11FB	0

#define CAP_MODE_80211A		1
#define CAP_MODE_80211B		2
#define CAP_MODE_80211G		4
#define CAP_MODE_MASK		7

#define WORK_LINK_UP		(1<<0)
#define WORK_LINK_DOWN		(1<<1)
#define WORK_SET_MULTICAST_LIST	(1<<2)
#define COMMAND_BUFFER_SIZE	(CONTROL_BUFFER_SIZE + sizeof(struct rndis_set))

static const struct ieee80211_channel rndis_channels[] = {
	{ .center_freq = 2412 },
	{ .center_freq = 2417 },
	{ .center_freq = 2422 },
	{ .center_freq = 2427 },
	{ .center_freq = 2432 },
	{ .center_freq = 2437 },
	{ .center_freq = 2442 },
	{ .center_freq = 2447 },
	{ .center_freq = 2452 },
	{ .center_freq = 2457 },
	{ .center_freq = 2462 },
	{ .center_freq = 2467 },
	{ .center_freq = 2472 },
	{ .center_freq = 2484 },
};

static const struct ieee80211_rate rndis_rates[] = {
	{ .bitrate = 10 },
	{ .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
	{ .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
	{ .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
	{ .bitrate = 60 },
	{ .bitrate = 90 },
	{ .bitrate = 120 },
	{ .bitrate = 180 },
	{ .bitrate = 240 },
	{ .bitrate = 360 },
	{ .bitrate = 480 },
	{ .bitrate = 540 }
};

/* RNDIS device private data */
	struct wireless_dev wdev;

	struct cfg80211_scan_request *scan_request;

	struct workqueue_struct *workqueue;
	struct delayed_work stats_work;
	struct delayed_work scan_work;
	struct work_struct work;
	struct mutex command_lock;
	spinlock_t stats_lock;
	unsigned long work_pending;

	struct ieee80211_supported_band band;
	struct ieee80211_channel channels[ARRAY_SIZE(rndis_channels)];
	struct ieee80211_rate rates[ARRAY_SIZE(rndis_rates)];

	struct iw_statistics iwstats;
	struct iw_statistics privstats;

	int caps;
	int multicast_size;

	/* module parameters */
	char param_country[4];
	int  param_frameburst;
	int  param_afterburner;
	int  param_power_save;
	int  param_power_output;
	int  param_roamtrigger;
	int  param_roamdelta;
	u32  param_workaround_interval;

	/* hardware state */
	int radio_on;
	int infra_mode;
	struct ndis_80211_ssid essid;

	/* encryption stuff */
	int  encr_tx_key_index;
	char encr_keys[4][32];
	int  encr_key_len[4];
	int  wpa_version;
	int  wpa_keymgmt;
	int  wpa_authalg;
	int  wpa_ie_len;
	u8  *wpa_ie;
	int  wpa_cipher_pair;
	int  wpa_cipher_group;

	u8 command_buffer[COMMAND_BUFFER_SIZE];
static int rndis_change_virtual_intf(struct wiphy *wiphy,
					struct net_device *dev,
					enum nl80211_iftype type, u32 *flags,
					struct vif_params *params);

static int rndis_scan(struct wiphy *wiphy, struct net_device *dev,
			struct cfg80211_scan_request *request);

static int rndis_set_wiphy_params(struct wiphy *wiphy, u32 changed);

static int rndis_set_tx_power(struct wiphy *wiphy, enum tx_power_setting type,
				int dbm);
static int rndis_get_tx_power(struct wiphy *wiphy, int *dbm);

static struct cfg80211_ops rndis_config_ops = {
	.change_virtual_intf = rndis_change_virtual_intf,
	.scan = rndis_scan,
	.set_wiphy_params = rndis_set_wiphy_params,
	.set_tx_power = rndis_set_tx_power,
	.get_tx_power = rndis_get_tx_power,
static void *rndis_wiphy_privid = &rndis_wiphy_privid;


static const unsigned char zero_bssid[ETH_ALEN] = {0,};
static const unsigned char ffff_bssid[ETH_ALEN] = { 0xff, 0xff, 0xff,
							0xff, 0xff, 0xff };


static struct rndis_wlan_private *get_rndis_wlan_priv(struct usbnet *dev)
	return (struct rndis_wlan_private *)dev->driver_priv;
static u32 get_bcm4320_power_dbm(struct rndis_wlan_private *priv)
	switch (priv->param_power_output) {
	default:
	case 3:
		return BCM4320_DEFAULT_TXPOWER_DBM_100;
	case 2:
		return BCM4320_DEFAULT_TXPOWER_DBM_75;
	case 1:
		return BCM4320_DEFAULT_TXPOWER_DBM_50;
	case 0:
		return BCM4320_DEFAULT_TXPOWER_DBM_25;
	}
#ifdef DEBUG
static const char *oid_to_string(__le32 oid)
{
	switch (oid) {
#define OID_STR(oid) case oid: return(#oid)
		/* from rndis_host.h */
		OID_STR(OID_802_3_PERMANENT_ADDRESS);
		OID_STR(OID_GEN_MAXIMUM_FRAME_SIZE);
		OID_STR(OID_GEN_CURRENT_PACKET_FILTER);
		OID_STR(OID_GEN_PHYSICAL_MEDIUM);

		/* from rndis_wlan.c */
		OID_STR(OID_GEN_LINK_SPEED);
		OID_STR(OID_GEN_RNDIS_CONFIG_PARAMETER);

		OID_STR(OID_GEN_XMIT_OK);
		OID_STR(OID_GEN_RCV_OK);
		OID_STR(OID_GEN_XMIT_ERROR);
		OID_STR(OID_GEN_RCV_ERROR);
		OID_STR(OID_GEN_RCV_NO_BUFFER);

		OID_STR(OID_802_3_CURRENT_ADDRESS);
		OID_STR(OID_802_3_MULTICAST_LIST);
		OID_STR(OID_802_3_MAXIMUM_LIST_SIZE);

		OID_STR(OID_802_11_BSSID);
		OID_STR(OID_802_11_SSID);
		OID_STR(OID_802_11_INFRASTRUCTURE_MODE);
		OID_STR(OID_802_11_ADD_WEP);
		OID_STR(OID_802_11_REMOVE_WEP);
		OID_STR(OID_802_11_DISASSOCIATE);
		OID_STR(OID_802_11_AUTHENTICATION_MODE);
		OID_STR(OID_802_11_PRIVACY_FILTER);
		OID_STR(OID_802_11_BSSID_LIST_SCAN);
		OID_STR(OID_802_11_ENCRYPTION_STATUS);
		OID_STR(OID_802_11_ADD_KEY);
		OID_STR(OID_802_11_REMOVE_KEY);
		OID_STR(OID_802_11_ASSOCIATION_INFORMATION);
		OID_STR(OID_802_11_PMKID);
		OID_STR(OID_802_11_NETWORK_TYPES_SUPPORTED);
		OID_STR(OID_802_11_NETWORK_TYPE_IN_USE);
		OID_STR(OID_802_11_TX_POWER_LEVEL);
		OID_STR(OID_802_11_RSSI);
		OID_STR(OID_802_11_RSSI_TRIGGER);
		OID_STR(OID_802_11_FRAGMENTATION_THRESHOLD);
		OID_STR(OID_802_11_RTS_THRESHOLD);
		OID_STR(OID_802_11_SUPPORTED_RATES);
		OID_STR(OID_802_11_CONFIGURATION);
		OID_STR(OID_802_11_BSSID_LIST);
#undef OID_STR
	}

	return "?";
}
#else
static const char *oid_to_string(__le32 oid)
{
	return "?";
}
#endif


/* translate error code */
static int rndis_error_status(__le32 rndis_status)
{
	int ret = -EINVAL;
	switch (rndis_status) {
	case RNDIS_STATUS_SUCCESS:
		ret = 0;
		break;
	case RNDIS_STATUS_FAILURE:
	case RNDIS_STATUS_INVALID_DATA:
		ret = -EINVAL;
		break;
	case RNDIS_STATUS_NOT_SUPPORTED:
		ret = -EOPNOTSUPP;
		break;
	case RNDIS_STATUS_ADAPTER_NOT_READY:
	case RNDIS_STATUS_ADAPTER_NOT_OPEN:
		ret = -EBUSY;
		break;
	}
	return ret;
}


static int rndis_query_oid(struct usbnet *dev, __le32 oid, void *data, int *len)
{
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(dev);
	union {
		void			*buf;
		struct rndis_msg_hdr	*header;
		struct rndis_query	*get;
		struct rndis_query_c	*get_c;
	} u;
	int ret, buflen;

	buflen = *len + sizeof(*u.get);
	if (buflen < CONTROL_BUFFER_SIZE)
		buflen = CONTROL_BUFFER_SIZE;

	if (buflen > COMMAND_BUFFER_SIZE) {
		u.buf = kmalloc(buflen, GFP_KERNEL);
		if (!u.buf)
			return -ENOMEM;
	} else {
		u.buf = priv->command_buffer;
	}

	mutex_lock(&priv->command_lock);

	memset(u.get, 0, sizeof *u.get);
	u.get->msg_type = RNDIS_MSG_QUERY;
	u.get->msg_len = cpu_to_le32(sizeof *u.get);
	ret = rndis_command(dev, u.header, buflen);
	if (ret < 0)
		devdbg(dev, "rndis_query_oid(%s): rndis_command() failed, %d "
			"(%08x)", oid_to_string(oid), ret,
			le32_to_cpu(u.get_c->status));

	if (ret == 0) {
		ret = le32_to_cpu(u.get_c->len);
		*len = (*len > ret) ? ret : *len;
		memcpy(data, u.buf + le32_to_cpu(u.get_c->offset) + 8, *len);
		ret = rndis_error_status(u.get_c->status);

		if (ret < 0)
			devdbg(dev, "rndis_query_oid(%s): device returned "
				"error,  0x%08x (%d)", oid_to_string(oid),
				le32_to_cpu(u.get_c->status), ret);
	mutex_unlock(&priv->command_lock);

	if (u.buf != priv->command_buffer)
		kfree(u.buf);
	return ret;
}


static int rndis_set_oid(struct usbnet *dev, __le32 oid, void *data, int len)
{
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(dev);
	union {
		void			*buf;
		struct rndis_msg_hdr	*header;
		struct rndis_set	*set;
		struct rndis_set_c	*set_c;
	} u;
	int ret, buflen;

	buflen = len + sizeof(*u.set);
	if (buflen < CONTROL_BUFFER_SIZE)
		buflen = CONTROL_BUFFER_SIZE;

	if (buflen > COMMAND_BUFFER_SIZE) {
		u.buf = kmalloc(buflen, GFP_KERNEL);
		if (!u.buf)
			return -ENOMEM;
	} else {
		u.buf = priv->command_buffer;
	}

	mutex_lock(&priv->command_lock);

	memset(u.set, 0, sizeof *u.set);
	u.set->msg_type = RNDIS_MSG_SET;
	u.set->msg_len = cpu_to_le32(sizeof(*u.set) + len);
	u.set->oid = oid;
	u.set->len = cpu_to_le32(len);
	u.set->offset = cpu_to_le32(sizeof(*u.set) - 8);
	u.set->handle = cpu_to_le32(0);
	memcpy(u.buf + sizeof(*u.set), data, len);

	ret = rndis_command(dev, u.header, buflen);
	if (ret < 0)
		devdbg(dev, "rndis_set_oid(%s): rndis_command() failed, %d "
			"(%08x)", oid_to_string(oid), ret,
			le32_to_cpu(u.set_c->status));

	if (ret == 0) {
		ret = rndis_error_status(u.set_c->status);

		if (ret < 0)
			devdbg(dev, "rndis_set_oid(%s): device returned error, "
				"0x%08x (%d)", oid_to_string(oid),
				le32_to_cpu(u.set_c->status), ret);
	}

	mutex_unlock(&priv->command_lock);

	if (u.buf != priv->command_buffer)
		kfree(u.buf);
static int rndis_reset(struct usbnet *usbdev)
{
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
	struct rndis_reset *reset;
	int ret;

	mutex_lock(&priv->command_lock);

	reset = (void *)priv->command_buffer;
	memset(reset, 0, sizeof(*reset));
	reset->msg_type = RNDIS_MSG_RESET;
	reset->msg_len = cpu_to_le32(sizeof(*reset));
	ret = rndis_command(usbdev, (void *)reset, CONTROL_BUFFER_SIZE);

	mutex_unlock(&priv->command_lock);

	if (ret < 0)
		return ret;
	return 0;
}


/*
 * Specs say that we can only set config parameters only soon after device
 * initialization.
 *   value_type: 0 = u32, 2 = unicode string
 */
static int rndis_set_config_parameter(struct usbnet *dev, char *param,
						int value_type, void *value)
{
	struct ndis_config_param *infobuf;
	int value_len, info_len, param_len, ret, i;
	__le16 *unibuf;
	__le32 *dst_value;

	if (value_type == 0)
		value_len = sizeof(__le32);
	else if (value_type == 2)
		value_len = strlen(value) * sizeof(__le16);
	else
		return -EINVAL;

	param_len = strlen(param) * sizeof(__le16);
	info_len = sizeof(*infobuf) + param_len + value_len;

#ifdef DEBUG
	info_len += 12;
#endif
	infobuf = kmalloc(info_len, GFP_KERNEL);
	if (!infobuf)
		return -ENOMEM;

#ifdef DEBUG
	info_len -= 12;
	/* extra 12 bytes are for padding (debug output) */
	memset(infobuf, 0xCC, info_len + 12);
#endif

	if (value_type == 2)
		devdbg(dev, "setting config parameter: %s, value: %s",
						param, (u8 *)value);
	else
		devdbg(dev, "setting config parameter: %s, value: %d",
						param, *(u32 *)value);

	infobuf->name_offs = cpu_to_le32(sizeof(*infobuf));
	infobuf->name_length = cpu_to_le32(param_len);
	infobuf->type = cpu_to_le32(value_type);
	infobuf->value_offs = cpu_to_le32(sizeof(*infobuf) + param_len);
	infobuf->value_length = cpu_to_le32(value_len);

	/* simple string to unicode string conversion */
	unibuf = (void *)infobuf + sizeof(*infobuf);
	for (i = 0; i < param_len / sizeof(__le16); i++)
		unibuf[i] = cpu_to_le16(param[i]);

	if (value_type == 2) {
		unibuf = (void *)infobuf + sizeof(*infobuf) + param_len;
		for (i = 0; i < value_len / sizeof(__le16); i++)
			unibuf[i] = cpu_to_le16(((u8 *)value)[i]);
	} else {
		dst_value = (void *)infobuf + sizeof(*infobuf) + param_len;
		*dst_value = cpu_to_le32(*(u32 *)value);
	}

#ifdef DEBUG
	devdbg(dev, "info buffer (len: %d):", info_len);
	for (i = 0; i < info_len; i += 12) {
		u32 *tmp = (u32 *)((u8 *)infobuf + i);
		devdbg(dev, "%08X:%08X:%08X",
			cpu_to_be32(tmp[0]),
			cpu_to_be32(tmp[1]),
			cpu_to_be32(tmp[2]));
	}
#endif

	ret = rndis_set_oid(dev, OID_GEN_RNDIS_CONFIG_PARAMETER,
							infobuf, info_len);
	if (ret != 0)
		devdbg(dev, "setting rndis config parameter failed, %d.", ret);

	kfree(infobuf);
	return ret;
}

static int rndis_set_config_parameter_str(struct usbnet *dev,
						char *param, char *value)
{
	return(rndis_set_config_parameter(dev, param, 2, value));
}

/*static int rndis_set_config_parameter_u32(struct usbnet *dev,
						char *param, u32 value)
{
	return(rndis_set_config_parameter(dev, param, 0, &value));
}*/


/*
 * data conversion functions
 */
static int level_to_qual(int level)
{
	int qual = 100 * (level - WL_NOISE) / (WL_SIGMAX - WL_NOISE);
	return qual >= 0 ? (qual <= 100 ? qual : 100) : 0;
}


static void dsconfig_to_freq(unsigned int dsconfig, struct iw_freq *freq)
{
	freq->e = 0;
	freq->i = 0;
	freq->flags = 0;

	/* see comment in wireless.h above the "struct iw_freq"
	 * definition for an explanation of this if
	 * NOTE: 1000000 is due to the kHz
	 */
	if (dsconfig > 1000000) {
		freq->m = dsconfig / 10;
		freq->e = 1;
	} else
		freq->m = dsconfig;

	/* convert from kHz to Hz */
	freq->e += 3;
}


static int freq_to_dsconfig(struct iw_freq *freq, unsigned int *dsconfig)
{
	if (freq->m < 1000 && freq->e == 0) {
		if (freq->m >= 1 && freq->m <= 14)
			*dsconfig = ieee80211_dsss_chan_to_freq(freq->m) * 1000;
		else
			return -1;
	} else {
		int i;
		*dsconfig = freq->m;
		for (i = freq->e; i > 0; i--)
			*dsconfig *= 10;
		*dsconfig /= 1000;
	}

	return 0;
}


/*
 * common functions
 */
static int
add_wep_key(struct usbnet *usbdev, char *key, int key_len, int index);

static int get_essid(struct usbnet *usbdev, struct ndis_80211_ssid *ssid)
{
	int ret, len;

	len = sizeof(*ssid);
	ret = rndis_query_oid(usbdev, OID_802_11_SSID, ssid, &len);

	if (ret != 0)

#ifdef DEBUG
	{
		unsigned char tmp[NDIS_802_11_LENGTH_SSID + 1];

		memcpy(tmp, ssid->essid, le32_to_cpu(ssid->length));
		tmp[le32_to_cpu(ssid->length)] = 0;
		devdbg(usbdev, "get_essid: '%s', ret: %d", tmp, ret);
	}
#endif
	return ret;
}


static int set_essid(struct usbnet *usbdev, struct ndis_80211_ssid *ssid)
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
	int ret;

	ret = rndis_set_oid(usbdev, OID_802_11_SSID, ssid, sizeof(*ssid));
	if (ret == 0) {
		memcpy(&priv->essid, ssid, sizeof(priv->essid));
		priv->radio_on = 1;
		devdbg(usbdev, "set_essid: radio_on = 1");
	}

	return ret;
}


static int get_bssid(struct usbnet *usbdev, u8 bssid[ETH_ALEN])
{
	int ret, len;

	len = ETH_ALEN;
	ret = rndis_query_oid(usbdev, OID_802_11_BSSID, bssid, &len);

	if (ret != 0)
		memset(bssid, 0, ETH_ALEN);

	return ret;
}

static int get_association_info(struct usbnet *usbdev,
			struct ndis_80211_assoc_info *info, int len)
{
	return rndis_query_oid(usbdev, OID_802_11_ASSOCIATION_INFORMATION,
				info, &len);
}

static int is_associated(struct usbnet *usbdev)
{
	u8 bssid[ETH_ALEN];
	int ret;

	ret = get_bssid(usbdev, bssid);

	return(ret == 0 && memcmp(bssid, zero_bssid, ETH_ALEN) != 0);
}


static int disassociate(struct usbnet *usbdev, int reset_ssid)
{
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
	struct ndis_80211_ssid ssid;
	int i, ret = 0;

	if (priv->radio_on) {
		ret = rndis_set_oid(usbdev, OID_802_11_DISASSOCIATE, NULL, 0);
		if (ret == 0) {
			priv->radio_on = 0;
			devdbg(usbdev, "disassociate: radio_on = 0");

			if (reset_ssid)
				msleep(100);
		}
	}

	/* disassociate causes radio to be turned off; if reset_ssid
	 * is given, set random ssid to enable radio */
	if (reset_ssid) {
		ssid.length = cpu_to_le32(sizeof(ssid.essid));
		get_random_bytes(&ssid.essid[2], sizeof(ssid.essid)-2);
		ssid.essid[0] = 0x1;
		ssid.essid[1] = 0xff;
		for (i = 2; i < sizeof(ssid.essid); i++)
			ssid.essid[i] = 0x1 + (ssid.essid[i] * 0xfe / 0xff);
		ret = set_essid(usbdev, &ssid);
	}
	return ret;
}


static int set_auth_mode(struct usbnet *usbdev, int wpa_version, int authalg)
{
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
	__le32 tmp;
	int auth_mode, ret;

	devdbg(usbdev, "set_auth_mode: wpa_version=0x%x authalg=0x%x "
		"keymgmt=0x%x", wpa_version, authalg, priv->wpa_keymgmt);

	if (wpa_version & IW_AUTH_WPA_VERSION_WPA2) {
		if (priv->wpa_keymgmt & IW_AUTH_KEY_MGMT_802_1X)
			auth_mode = NDIS_80211_AUTH_WPA2;
			auth_mode = NDIS_80211_AUTH_WPA2_PSK;
	} else if (wpa_version & IW_AUTH_WPA_VERSION_WPA) {
		if (priv->wpa_keymgmt & IW_AUTH_KEY_MGMT_802_1X)
			auth_mode = NDIS_80211_AUTH_WPA;
		else if (priv->wpa_keymgmt & IW_AUTH_KEY_MGMT_PSK)
			auth_mode = NDIS_80211_AUTH_WPA_PSK;
			auth_mode = NDIS_80211_AUTH_WPA_NONE;
	} else if (authalg & IW_AUTH_ALG_SHARED_KEY) {
		if (authalg & IW_AUTH_ALG_OPEN_SYSTEM)
			auth_mode = NDIS_80211_AUTH_AUTO_SWITCH;
			auth_mode = NDIS_80211_AUTH_SHARED;
		auth_mode = NDIS_80211_AUTH_OPEN;

	tmp = cpu_to_le32(auth_mode);
	ret = rndis_set_oid(usbdev, OID_802_11_AUTHENTICATION_MODE, &tmp,
								sizeof(tmp));
	if (ret != 0) {
		devwarn(usbdev, "setting auth mode failed (%08X)", ret);
		return ret;
	}

	priv->wpa_version = wpa_version;
	priv->wpa_authalg = authalg;
	return 0;
}


static int set_priv_filter(struct usbnet *usbdev)
{
	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);