summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/legacy/rndis_wlan.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/legacy/rndis_wlan.c')
-rw-r--r--drivers/net/wireless/legacy/rndis_wlan.c3760
1 files changed, 3760 insertions, 0 deletions
diff --git a/drivers/net/wireless/legacy/rndis_wlan.c b/drivers/net/wireless/legacy/rndis_wlan.c
new file mode 100644
index 000000000000..712038d46bdb
--- /dev/null
+++ b/drivers/net/wireless/legacy/rndis_wlan.c
@@ -0,0 +1,3760 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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@iki.fi>
+ *
+ * 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/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/ieee80211.h>
+#include <linux/if_arp.h>
+#include <linux/ctype.h>
+#include <linux/spinlock.h>
+#include <linux/slab.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;
+module_param_named(workaround_interval, modparam_workaround_interval,
+ int, 0444);
+MODULE_PARM_DESC(workaround_interval,
+ "set stall workaround interval in msecs (0=disabled) (default: 0)");
+
+/* 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
+
+/* Known device types */
+#define RNDIS_UNKNOWN 0
+#define RNDIS_BCM4320A 1
+#define RNDIS_BCM4320B 2
+
+
+/* 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
+
+enum ndis_80211_net_type {
+ NDIS_80211_TYPE_FREQ_HOP,
+ NDIS_80211_TYPE_DIRECT_SEQ,
+ NDIS_80211_TYPE_OFDM_A,
+ NDIS_80211_TYPE_OFDM_G
+};
+
+enum ndis_80211_net_infra {
+ NDIS_80211_INFRA_ADHOC,
+ NDIS_80211_INFRA_INFRA,
+ NDIS_80211_INFRA_AUTO_UNKNOWN
+};
+
+enum ndis_80211_auth_mode {
+ 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
+};
+
+enum ndis_80211_encr_status {
+ 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
+};
+
+enum ndis_80211_priv_filter {
+ NDIS_80211_PRIV_ACCEPT_ALL,
+ NDIS_80211_PRIV_8021X_WEP
+};
+
+enum ndis_80211_status_type {
+ NDIS_80211_STATUSTYPE_AUTHENTICATION,
+ NDIS_80211_STATUSTYPE_MEDIASTREAMMODE,
+ NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST,
+ NDIS_80211_STATUSTYPE_RADIOSTATE,
+};
+
+enum ndis_80211_media_stream_mode {
+ NDIS_80211_MEDIA_STREAM_OFF,
+ NDIS_80211_MEDIA_STREAM_ON
+};
+
+enum ndis_80211_radio_status {
+ NDIS_80211_RADIO_STATUS_ON,
+ NDIS_80211_RADIO_STATUS_HARDWARE_OFF,
+ NDIS_80211_RADIO_STATUS_SOFTWARE_OFF,
+};
+
+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)
+};
+
+enum ndis_80211_power_mode {
+ NDIS_80211_POWER_MODE_CAM,
+ NDIS_80211_POWER_MODE_MAX_PSP,
+ NDIS_80211_POWER_MODE_FAST_PSP,
+};
+
+enum ndis_80211_pmkid_cand_list_flag_bits {
+ NDIS_80211_PMKID_CAND_PREAUTH = cpu_to_le32(1 << 0)
+};
+
+struct ndis_80211_auth_request {
+ __le32 length;
+ u8 bssid[ETH_ALEN];
+ u8 padding[2];
+ __le32 flags;
+} __packed;
+
+struct ndis_80211_pmkid_candidate {
+ u8 bssid[ETH_ALEN];
+ u8 padding[2];
+ __le32 flags;
+} __packed;
+
+struct ndis_80211_pmkid_cand_list {
+ __le32 version;
+ __le32 num_candidates;
+ struct ndis_80211_pmkid_candidate candidate_list[];
+} __packed;
+
+struct ndis_80211_status_indication {
+ __le32 status_type;
+ union {
+ __le32 media_stream_mode;
+ __le32 radio_status;
+ DECLARE_FLEX_ARRAY(struct ndis_80211_auth_request, auth_request);
+ struct ndis_80211_pmkid_cand_list cand_list;
+ } u;
+} __packed;
+
+struct ndis_80211_ssid {
+ __le32 length;
+ u8 essid[NDIS_802_11_LENGTH_SSID];
+} __packed;
+
+struct ndis_80211_conf_freq_hop {
+ __le32 length;
+ __le32 hop_pattern;
+ __le32 hop_set;
+ __le32 dwell_time;
+} __packed;
+
+struct ndis_80211_conf {
+ __le32 length;
+ __le32 beacon_period;
+ __le32 atim_window;
+ __le32 ds_config;
+ struct ndis_80211_conf_freq_hop fh_config;
+} __packed;
+
+struct ndis_80211_bssid_ex {
+ __le32 length;
+ u8 mac[ETH_ALEN];
+ 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[];
+} __packed;
+
+struct ndis_80211_bssid_list_ex {
+ __le32 num_items;
+ u8 bssid_data[];
+} __packed;
+
+struct ndis_80211_fixed_ies {
+ u8 timestamp[8];
+ __le16 beacon_interval;
+ __le16 capabilities;
+} __packed;
+
+struct ndis_80211_wep_key {
+ __le32 size;
+ __le32 index;
+ __le32 length;
+ u8 material[32];
+} __packed;
+
+struct ndis_80211_key {
+ __le32 size;
+ __le32 index;
+ __le32 length;
+ u8 bssid[ETH_ALEN];
+ u8 padding[6];
+ u8 rsc[8];
+ u8 material[32];
+} __packed;
+
+struct ndis_80211_remove_key {
+ __le32 size;
+ __le32 index;
+ u8 bssid[ETH_ALEN];
+ u8 padding[2];
+} __packed;
+
+struct ndis_config_param {
+ __le32 name_offs;
+ __le32 name_length;
+ __le32 type;
+ __le32 value_offs;
+ __le32 value_length;
+} __packed;
+
+struct ndis_80211_assoc_info {
+ __le32 length;
+ __le16 req_ies;
+ struct req_ie {
+ __le16 capa;
+ __le16 listen_interval;
+ u8 cur_ap_address[ETH_ALEN];
+ } 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;
+} __packed;
+
+struct ndis_80211_capability {
+ __le32 length;
+ __le32 version;
+ __le32 num_pmkids;
+ __le32 num_auth_encr_pair;
+} __packed;
+
+struct ndis_80211_bssid_info {
+ u8 bssid[ETH_ALEN];
+ u8 pmkid[16];
+} __packed;
+
+struct ndis_80211_pmkid {
+ __le32 length;
+ __le32 bssid_info_count;
+ struct ndis_80211_bssid_info bssid_info[];
+} __packed;
+
+/*
+ * private data
+ */
+#define CAP_MODE_80211A 1
+#define CAP_MODE_80211B 2
+#define CAP_MODE_80211G 4
+#define CAP_MODE_MASK 7
+
+#define WORK_LINK_UP 0
+#define WORK_LINK_DOWN 1
+#define WORK_SET_MULTICAST_LIST 2
+
+#define RNDIS_WLAN_ALG_NONE 0
+#define RNDIS_WLAN_ALG_WEP (1<<0)
+#define RNDIS_WLAN_ALG_TKIP (1<<1)
+#define RNDIS_WLAN_ALG_CCMP (1<<2)
+
+#define RNDIS_WLAN_NUM_KEYS 4
+#define RNDIS_WLAN_KEY_MGMT_NONE 0
+#define RNDIS_WLAN_KEY_MGMT_802_1X (1<<0)
+#define RNDIS_WLAN_KEY_MGMT_PSK (1<<1)
+
+#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 }
+};
+
+static const u32 rndis_cipher_suites[] = {
+ WLAN_CIPHER_SUITE_WEP40,
+ WLAN_CIPHER_SUITE_WEP104,
+ WLAN_CIPHER_SUITE_TKIP,
+ WLAN_CIPHER_SUITE_CCMP,
+};
+
+struct rndis_wlan_encr_key {
+ int len;
+ u32 cipher;
+ u8 material[32];
+ u8 bssid[ETH_ALEN];
+ bool pairwise;
+ bool tx_key;
+};
+
+/* RNDIS device private data */
+struct rndis_wlan_private {
+ struct usbnet *usbdev;
+
+ struct wireless_dev wdev;
+
+ struct cfg80211_scan_request *scan_request;
+
+ struct workqueue_struct *workqueue;
+ struct delayed_work dev_poller_work;
+ struct delayed_work scan_work;
+ struct work_struct work;
+ struct mutex command_lock;
+ unsigned long work_pending;
+ int last_qual;
+ s32 cqm_rssi_thold;
+ u32 cqm_rssi_hyst;
+ int last_cqm_event_rssi;
+
+ struct ieee80211_supported_band band;
+ struct ieee80211_channel channels[ARRAY_SIZE(rndis_channels)];
+ struct ieee80211_rate rates[ARRAY_SIZE(rndis_rates)];
+ u32 cipher_suites[ARRAY_SIZE(rndis_cipher_suites)];
+
+ int device_type;
+ 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 */
+ bool radio_on;
+ int power_mode;
+ int infra_mode;
+ bool connected;
+ u8 bssid[ETH_ALEN];
+ u32 current_command_oid;
+
+ /* encryption stuff */
+ u8 encr_tx_key_index;
+ struct rndis_wlan_encr_key encr_keys[RNDIS_WLAN_NUM_KEYS];
+ int wpa_version;
+
+ u8 command_buffer[COMMAND_BUFFER_SIZE];
+};
+
+/*
+ * cfg80211 ops
+ */
+static int rndis_change_virtual_intf(struct wiphy *wiphy,
+ struct net_device *dev,
+ enum nl80211_iftype type,
+ struct vif_params *params);
+
+static int rndis_scan(struct wiphy *wiphy,
+ 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,
+ struct wireless_dev *wdev,
+ enum nl80211_tx_power_setting type,
+ int mbm);
+static int rndis_get_tx_power(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ int *dbm);
+
+static int rndis_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+
+static int rndis_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code);
+
+static int rndis_join_ibss(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_ibss_params *params);
+
+static int rndis_leave_ibss(struct wiphy *wiphy, struct net_device *dev);
+
+static int rndis_add_key(struct wiphy *wiphy, struct net_device *netdev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *mac_addr, struct key_params *params);
+
+static int rndis_del_key(struct wiphy *wiphy, struct net_device *netdev,
+ int link_id, u8 key_index, bool pairwise,
+ const u8 *mac_addr);
+
+static int rndis_set_default_key(struct wiphy *wiphy, struct net_device *netdev,
+ int link_id, u8 key_index, bool unicast,
+ bool multicast);
+
+static int rndis_get_station(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *mac, struct station_info *sinfo);
+
+static int rndis_dump_station(struct wiphy *wiphy, struct net_device *dev,
+ int idx, u8 *mac, struct station_info *sinfo);
+
+static int rndis_set_pmksa(struct wiphy *wiphy, struct net_device *netdev,
+ struct cfg80211_pmksa *pmksa);
+
+static int rndis_del_pmksa(struct wiphy *wiphy, struct net_device *netdev,
+ struct cfg80211_pmksa *pmksa);
+
+static int rndis_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev);
+
+static int rndis_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
+ bool enabled, int timeout);
+
+static int rndis_set_cqm_rssi_config(struct wiphy *wiphy,
+ struct net_device *dev,
+ s32 rssi_thold, u32 rssi_hyst);
+
+static const 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,
+ .connect = rndis_connect,
+ .disconnect = rndis_disconnect,
+ .join_ibss = rndis_join_ibss,
+ .leave_ibss = rndis_leave_ibss,
+ .add_key = rndis_add_key,
+ .del_key = rndis_del_key,
+ .set_default_key = rndis_set_default_key,
+ .get_station = rndis_get_station,
+ .dump_station = rndis_dump_station,
+ .set_pmksa = rndis_set_pmksa,
+ .del_pmksa = rndis_del_pmksa,
+ .flush_pmksa = rndis_flush_pmksa,
+ .set_power_mgmt = rndis_set_power_mgmt,
+ .set_cqm_rssi_config = rndis_set_cqm_rssi_config,
+};
+
+static void *rndis_wiphy_privid = &rndis_wiphy_privid;
+
+
+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;
+ }
+}
+
+static bool is_wpa_key(struct rndis_wlan_private *priv, u8 idx)
+{
+ int cipher = priv->encr_keys[idx].cipher;
+
+ return (cipher == WLAN_CIPHER_SUITE_CCMP ||
+ cipher == WLAN_CIPHER_SUITE_TKIP);
+}
+
+static int rndis_cipher_to_alg(u32 cipher)
+{
+ switch (cipher) {
+ default:
+ return RNDIS_WLAN_ALG_NONE;
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ return RNDIS_WLAN_ALG_WEP;
+ case WLAN_CIPHER_SUITE_TKIP:
+ return RNDIS_WLAN_ALG_TKIP;
+ case WLAN_CIPHER_SUITE_CCMP:
+ return RNDIS_WLAN_ALG_CCMP;
+ }
+}
+
+static int rndis_akm_suite_to_key_mgmt(u32 akm_suite)
+{
+ switch (akm_suite) {
+ default:
+ return RNDIS_WLAN_KEY_MGMT_NONE;
+ case WLAN_AKM_SUITE_8021X:
+ return RNDIS_WLAN_KEY_MGMT_802_1X;
+ case WLAN_AKM_SUITE_PSK:
+ return RNDIS_WLAN_KEY_MGMT_PSK;
+ }
+}
+
+#ifdef DEBUG
+static const char *oid_to_string(u32 oid)
+{
+ switch (oid) {
+#define OID_STR(oid) case oid: return(#oid)
+ /* from rndis_host.h */
+ OID_STR(RNDIS_OID_802_3_PERMANENT_ADDRESS);
+ OID_STR(RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE);
+ OID_STR(RNDIS_OID_GEN_CURRENT_PACKET_FILTER);
+ OID_STR(RNDIS_OID_GEN_PHYSICAL_MEDIUM);
+
+ /* from rndis_wlan.c */
+ OID_STR(RNDIS_OID_GEN_LINK_SPEED);
+ OID_STR(RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER);
+
+ OID_STR(RNDIS_OID_GEN_XMIT_OK);
+ OID_STR(RNDIS_OID_GEN_RCV_OK);
+ OID_STR(RNDIS_OID_GEN_XMIT_ERROR);
+ OID_STR(RNDIS_OID_GEN_RCV_ERROR);
+ OID_STR(RNDIS_OID_GEN_RCV_NO_BUFFER);
+
+ OID_STR(RNDIS_OID_802_3_CURRENT_ADDRESS);
+ OID_STR(RNDIS_OID_802_3_MULTICAST_LIST);
+ OID_STR(RNDIS_OID_802_3_MAXIMUM_LIST_SIZE);
+
+ OID_STR(RNDIS_OID_802_11_BSSID);
+ OID_STR(RNDIS_OID_802_11_SSID);
+ OID_STR(RNDIS_OID_802_11_INFRASTRUCTURE_MODE);
+ OID_STR(RNDIS_OID_802_11_ADD_WEP);
+ OID_STR(RNDIS_OID_802_11_REMOVE_WEP);
+ OID_STR(RNDIS_OID_802_11_DISASSOCIATE);
+ OID_STR(RNDIS_OID_802_11_AUTHENTICATION_MODE);
+ OID_STR(RNDIS_OID_802_11_PRIVACY_FILTER);
+ OID_STR(RNDIS_OID_802_11_BSSID_LIST_SCAN);
+ OID_STR(RNDIS_OID_802_11_ENCRYPTION_STATUS);
+ OID_STR(RNDIS_OID_802_11_ADD_KEY);
+ OID_STR(RNDIS_OID_802_11_REMOVE_KEY);
+ OID_STR(RNDIS_OID_802_11_ASSOCIATION_INFORMATION);
+ OID_STR(RNDIS_OID_802_11_CAPABILITY);
+ OID_STR(RNDIS_OID_802_11_PMKID);
+ OID_STR(RNDIS_OID_802_11_NETWORK_TYPES_SUPPORTED);
+ OID_STR(RNDIS_OID_802_11_NETWORK_TYPE_IN_USE);
+ OID_STR(RNDIS_OID_802_11_TX_POWER_LEVEL);
+ OID_STR(RNDIS_OID_802_11_RSSI);
+ OID_STR(RNDIS_OID_802_11_RSSI_TRIGGER);
+ OID_STR(RNDIS_OID_802_11_FRAGMENTATION_THRESHOLD);
+ OID_STR(RNDIS_OID_802_11_RTS_THRESHOLD);
+ OID_STR(RNDIS_OID_802_11_SUPPORTED_RATES);
+ OID_STR(RNDIS_OID_802_11_CONFIGURATION);
+ OID_STR(RNDIS_OID_802_11_POWER_MODE);
+ OID_STR(RNDIS_OID_802_11_BSSID_LIST);
+#undef OID_STR
+ }
+
+ return "?";
+}
+#else
+static const char *oid_to_string(u32 oid)
+{
+ return "?";
+}
+#endif
+
+/* translate error code */
+static int rndis_error_status(__le32 rndis_status)
+{
+ int ret = -EINVAL;
+ switch (le32_to_cpu(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, u32 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;
+ size_t buflen, resplen, respoffs, copylen;
+
+ 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 = cpu_to_le32(RNDIS_MSG_QUERY);
+ u.get->msg_len = cpu_to_le32(sizeof *u.get);
+ u.get->oid = cpu_to_le32(oid);
+
+ priv->current_command_oid = oid;
+ ret = rndis_command(dev, u.header, buflen);
+ priv->current_command_oid = 0;
+ if (ret < 0)
+ netdev_dbg(dev->net, "%s(%s): rndis_command() failed, %d (%08x)\n",
+ __func__, oid_to_string(oid), ret,
+ le32_to_cpu(u.get_c->status));
+
+ if (ret == 0) {
+ resplen = le32_to_cpu(u.get_c->len);
+ respoffs = le32_to_cpu(u.get_c->offset) + 8;
+
+ if (respoffs > buflen) {
+ /* Device returned data offset outside buffer, error. */
+ netdev_dbg(dev->net,
+ "%s(%s): received invalid data offset: %zu > %zu\n",
+ __func__, oid_to_string(oid), respoffs, buflen);
+
+ ret = -EINVAL;
+ goto exit_unlock;
+ }
+
+ copylen = min(resplen, buflen - respoffs);
+
+ if (copylen > *len)
+ copylen = *len;
+
+ memcpy(data, u.buf + respoffs, copylen);
+
+ *len = resplen;
+
+ ret = rndis_error_status(u.get_c->status);
+ if (ret < 0)
+ netdev_dbg(dev->net, "%s(%s): device returned error, 0x%08x (%d)\n",
+ __func__, oid_to_string(oid),
+ le32_to_cpu(u.get_c->status), ret);
+ }
+
+exit_unlock:
+ mutex_unlock(&priv->command_lock);
+
+ if (u.buf != priv->command_buffer)
+ kfree(u.buf);
+ return ret;
+}
+
+static int rndis_set_oid(struct usbnet *dev, u32 oid, const 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 = cpu_to_le32(RNDIS_MSG_SET);
+ u.set->msg_len = cpu_to_le32(sizeof(*u.set) + len);
+ u.set->oid = cpu_to_le32(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);
+
+ priv->current_command_oid = oid;
+ ret = rndis_command(dev, u.header, buflen);
+ priv->current_command_oid = 0;
+ if (ret < 0)
+ netdev_dbg(dev->net, "%s(%s): rndis_command() failed, %d (%08x)\n",
+ __func__, 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)
+ netdev_dbg(dev->net, "%s(%s): device returned error, 0x%08x (%d)\n",
+ __func__, 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);
+ return ret;
+}
+
+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 = cpu_to_le32(RNDIS_MSG_RESET);
+ reset->msg_len = cpu_to_le32(sizeof(*reset));
+ priv->current_command_oid = 0;
+ 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)
+ netdev_dbg(dev->net, "setting config parameter: %s, value: %s\n",
+ param, (u8 *)value);
+ else
+ netdev_dbg(dev->net, "setting config parameter: %s, value: %d\n",
+ 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
+ netdev_dbg(dev->net, "info buffer (len: %d)\n", info_len);
+ for (i = 0; i < info_len; i += 12) {
+ u32 *tmp = (u32 *)((u8 *)infobuf + i);
+ netdev_dbg(dev->net, "%08X:%08X:%08X\n",
+ cpu_to_be32(tmp[0]),
+ cpu_to_be32(tmp[1]),
+ cpu_to_be32(tmp[2]));
+ }
+#endif
+
+ ret = rndis_set_oid(dev, RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER,
+ infobuf, info_len);
+ if (ret != 0)
+ netdev_dbg(dev->net, "setting rndis config parameter failed, %d\n",
+ 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);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * common functions
+ */
+static int set_infra_mode(struct usbnet *usbdev, int mode);
+static void restore_keys(struct usbnet *usbdev);
+static int rndis_check_bssid_list(struct usbnet *usbdev, u8 *match_bssid,
+ bool *matched);
+
+static int rndis_start_bssid_list_scan(struct usbnet *usbdev)
+{
+ __le32 tmp;
+
+ /* Note: RNDIS_OID_802_11_BSSID_LIST_SCAN clears internal BSS list. */
+ tmp = cpu_to_le32(1);
+ return rndis_set_oid(usbdev, RNDIS_OID_802_11_BSSID_LIST_SCAN, &tmp,
+ sizeof(tmp));
+}
+
+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, RNDIS_OID_802_11_SSID,
+ ssid, sizeof(*ssid));
+ if (ret < 0) {
+ netdev_warn(usbdev->net, "setting SSID failed (%08X)\n", ret);
+ return ret;
+ }
+ if (ret == 0) {
+ priv->radio_on = true;
+ netdev_dbg(usbdev->net, "%s(): radio_on = true\n", __func__);
+ }
+
+ return ret;
+}
+
+static int set_bssid(struct usbnet *usbdev, const u8 *bssid)
+{
+ int ret;
+
+ ret = rndis_set_oid(usbdev, RNDIS_OID_802_11_BSSID,
+ bssid, ETH_ALEN);
+ if (ret < 0) {
+ netdev_warn(usbdev->net, "setting BSSID[%pM] failed (%08X)\n",
+ bssid, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int clear_bssid(struct usbnet *usbdev)
+{
+ static const u8 broadcast_mac[ETH_ALEN] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+ return set_bssid(usbdev, broadcast_mac);
+}
+
+static int get_bssid(struct usbnet *usbdev, u8 bssid[ETH_ALEN])
+{
+ int ret, len;
+
+ len = ETH_ALEN;
+ ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_BSSID,
+ bssid, &len);
+
+ if (ret != 0)
+ eth_zero_addr(bssid);
+
+ return ret;
+}
+
+static int get_association_info(struct usbnet *usbdev,
+ struct ndis_80211_assoc_info *info, int len)
+{
+ return rndis_query_oid(usbdev,
+ RNDIS_OID_802_11_ASSOCIATION_INFORMATION,
+ info, &len);
+}
+
+static bool is_associated(struct usbnet *usbdev)
+{
+ struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
+ u8 bssid[ETH_ALEN];
+
+ if (!priv->radio_on)
+ return false;
+
+ return (get_bssid(usbdev, bssid) == 0 && !is_zero_ether_addr(bssid));
+}
+
+static int disassociate(struct usbnet *usbdev, bool 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,
+ RNDIS_OID_802_11_DISASSOCIATE,
+ NULL, 0);
+ if (ret == 0) {
+ priv->radio_on = false;
+ netdev_dbg(usbdev->net, "%s(): radio_on = false\n",
+ __func__);
+
+ 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) {
+ /* Set device to infrastructure mode so we don't get ad-hoc
+ * 'media connect' indications with the random ssid.
+ */
+ set_infra_mode(usbdev, NDIS_80211_INFRA_INFRA);
+
+ 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, u32 wpa_version,
+ enum nl80211_auth_type auth_type, int keymgmt)
+{
+ struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
+ __le32 tmp;
+ int auth_mode, ret;
+
+ netdev_dbg(usbdev->net, "%s(): wpa_version=0x%x authalg=0x%x keymgmt=0x%x\n",
+ __func__, wpa_version, auth_type, keymgmt);
+
+ if (wpa_version & NL80211_WPA_VERSION_2) {
+ if (keymgmt & RNDIS_WLAN_KEY_MGMT_802_1X)
+ auth_mode = NDIS_80211_AUTH_WPA2;
+ else
+ auth_mode = NDIS_80211_AUTH_WPA2_PSK;
+ } else if (wpa_version & NL80211_WPA_VERSION_1) {
+ if (keymgmt & RNDIS_WLAN_KEY_MGMT_802_1X)
+ auth_mode = NDIS_80211_AUTH_WPA;
+ else if (keymgmt & RNDIS_WLAN_KEY_MGMT_PSK)
+ auth_mode = NDIS_80211_AUTH_WPA_PSK;
+ else
+ auth_mode = NDIS_80211_AUTH_WPA_NONE;
+ } else if (auth_type == NL80211_AUTHTYPE_SHARED_KEY)
+ auth_mode = NDIS_80211_AUTH_SHARED;
+ else if (auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM)
+ auth_mode = NDIS_80211_AUTH_OPEN;
+ else if (auth_type == NL80211_AUTHTYPE_AUTOMATIC)
+ auth_mode = NDIS_80211_AUTH_AUTO_SWITCH;
+ else
+ return -ENOTSUPP;
+
+ tmp = cpu_to_le32(auth_mode);
+ ret = rndis_set_oid(usbdev,
+ RNDIS_OID_802_11_AUTHENTICATION_MODE,
+ &tmp, sizeof(tmp));
+ if (ret != 0) {
+ netdev_warn(usbdev->net, "setting auth mode failed (%08X)\n",
+ ret);
+ return ret;
+ }
+
+ priv->wpa_version = wpa_version;
+
+ return 0;
+}
+
+static int set_priv_filter(struct usbnet *usbdev)
+{
+ struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
+ __le32 tmp;
+
+ netdev_dbg(usbdev->net, "%s(): wpa_version=0x%x\n",
+ __func__, priv->wpa_version);
+
+ if (priv->wpa_version & NL80211_WPA_VERSION_2 ||
+ priv->wpa_version & NL80211_WPA_VERSION_1)
+ tmp = cpu_to_le32(NDIS_80211_PRIV_8021X_WEP);
+ else
+ tmp = cpu_to_le32(NDIS_80211_PRIV_ACCEPT_ALL);
+
+ return rndis_set_oid(usbdev,
+ RNDIS_OID_802_11_PRIVACY_FILTER, &tmp,
+ sizeof(tmp));
+}
+
+static int set_encr_mode(struct usbnet *usbdev, int pairwise, int groupwise)
+{
+ __le32 tmp;
+ int encr_mode, ret;
+
+ netdev_dbg(usbdev->net, "%s(): cipher_pair=0x%x cipher_group=0x%x\n",
+ __func__, pairwise, groupwise);
+
+ if (pairwise & RNDIS_WLAN_ALG_CCMP)
+ encr_mode = NDIS_80211_ENCR_CCMP_ENABLED;
+ else if (pairwise & RNDIS_WLAN_ALG_TKIP)
+ encr_mode = NDIS_80211_ENCR_TKIP_ENABLED;
+ else if (pairwise & RNDIS_WLAN_ALG_WEP)
+ encr_mode = NDIS_80211_ENCR_WEP_ENABLED;
+ else if (groupwise & RNDIS_WLAN_ALG_CCMP)
+ encr_mode = NDIS_80211_ENCR_CCMP_ENABLED;
+ else if (groupwise & RNDIS_WLAN_ALG_TKIP)
+ encr_mode = NDIS_80211_ENCR_TKIP_ENABLED;
+ else
+ encr_mode = NDIS_80211_ENCR_DISABLED;
+
+ tmp = cpu_to_le32(encr_mode);
+ ret = rndis_set_oid(usbdev,