diff options
Diffstat (limited to 'drivers/net/wireless/rndis_wlan.c')
| -rw-r--r-- | drivers/net/wireless/rndis_wlan.c | 370 | 
1 files changed, 347 insertions, 23 deletions
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 1de5b22d3ef..babdcdf6d71 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c @@ -118,6 +118,7 @@ MODULE_PARM_DESC(workaround_interval,  #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_CAPABILITY			cpu_to_le32(0x0d010122)  #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) @@ -359,6 +360,30 @@ struct ndis_80211_assoc_info {  	__le32 offset_resp_ies;  } __attribute__((packed)); +struct ndis_80211_auth_encr_pair { +	__le32 auth_mode; +	__le32 encr_mode; +} __attribute__((packed)); + +struct ndis_80211_capability { +	__le32 length; +	__le32 version; +	__le32 num_pmkids; +	__le32 num_auth_encr_pair; +	struct ndis_80211_auth_encr_pair auth_encr_pair[0]; +} __attribute__((packed)); + +struct ndis_80211_bssid_info { +	u8 bssid[6]; +	u8 pmkid[16]; +}; + +struct ndis_80211_pmkid { +	__le32 length; +	__le32 bssid_info_count; +	struct ndis_80211_bssid_info bssid_info[0]; +}; +  /*   *  private data   */ @@ -477,13 +502,7 @@ struct rndis_wlan_private {  	/* encryption stuff */  	int  encr_tx_key_index;  	struct rndis_wlan_encr_key encr_keys[4]; -	enum nl80211_auth_type wpa_auth_type;  	int  wpa_version; -	int  wpa_keymgmt; -	int  wpa_ie_len; -	u8  *wpa_ie; -	int  wpa_cipher_pair; -	int  wpa_cipher_group;  	u8 command_buffer[COMMAND_BUFFER_SIZE];  }; @@ -535,6 +554,14 @@ static int rndis_get_station(struct wiphy *wiphy, struct net_device *dev,  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 struct cfg80211_ops rndis_config_ops = {  	.change_virtual_intf = rndis_change_virtual_intf,  	.scan = rndis_scan, @@ -551,6 +578,9 @@ static struct cfg80211_ops rndis_config_ops = {  	.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,  };  static void *rndis_wiphy_privid = &rndis_wiphy_privid; @@ -705,6 +735,7 @@ static int rndis_query_oid(struct usbnet *dev, __le32 oid, void *data, int *len)  		struct rndis_query_c	*get_c;  	} u;  	int ret, buflen; +	int resplen, respoffs, copylen;  	buflen = *len + sizeof(*u.get);  	if (buflen < CONTROL_BUFFER_SIZE) @@ -734,11 +765,34 @@ static int rndis_query_oid(struct usbnet *dev, __le32 oid, void *data, int *len)  			   le32_to_cpu(u.get_c->status));  	if (ret == 0) { -		memcpy(data, u.buf + le32_to_cpu(u.get_c->offset) + 8, *len); +		resplen = le32_to_cpu(u.get_c->len); +		respoffs = le32_to_cpu(u.get_c->offset) + 8; -		ret = le32_to_cpu(u.get_c->len); -		if (ret > *len) -			*len = ret; +		if (respoffs > buflen) { +			/* Device returned data offset outside buffer, error. */ +			netdev_dbg(dev->net, "%s(%s): received invalid " +				"data offset: %d > %d\n", __func__, +				oid_to_string(oid), respoffs, buflen); + +			ret = -EINVAL; +			goto exit_unlock; +		} + +		if ((resplen + respoffs) > buflen) { +			/* Device would have returned more data if buffer would +			 * have been big enough. Copy just the bits that we got. +			 */ +			copylen = buflen - respoffs; +		} else { +			copylen = resplen; +		} + +		if (copylen > *len) +			copylen = *len; + +		memcpy(data, u.buf + respoffs, copylen); + +		*len = resplen;  		ret = rndis_error_status(u.get_c->status);  		if (ret < 0) @@ -747,6 +801,7 @@ static int rndis_query_oid(struct usbnet *dev, __le32 oid, void *data, int *len)  				   le32_to_cpu(u.get_c->status), ret);  	} +exit_unlock:  	mutex_unlock(&priv->command_lock);  	if (u.buf != priv->command_buffer) @@ -1092,8 +1147,6 @@ static int set_auth_mode(struct usbnet *usbdev, u32 wpa_version,  	}  	priv->wpa_version = wpa_version; -	priv->wpa_auth_type = auth_type; -	priv->wpa_keymgmt = keymgmt;  	return 0;  } @@ -1118,7 +1171,6 @@ static int set_priv_filter(struct usbnet *usbdev)  static int set_encr_mode(struct usbnet *usbdev, int pairwise, int groupwise)  { -	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);  	__le32 tmp;  	int encr_mode, ret; @@ -1147,8 +1199,6 @@ static int set_encr_mode(struct usbnet *usbdev, int pairwise, int groupwise)  		return ret;  	} -	priv->wpa_cipher_pair = pairwise; -	priv->wpa_cipher_group = groupwise;  	return 0;  } @@ -1496,7 +1546,7 @@ static int remove_key(struct usbnet *usbdev, int index, const u8 *bssid)  static void set_multicast_list(struct usbnet *usbdev)  {  	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); -	struct dev_mc_list *mclist; +	struct netdev_hw_addr *ha;  	__le32 filter, basefilter;  	int ret;  	char *mc_addrs = NULL; @@ -1535,9 +1585,9 @@ static void set_multicast_list(struct usbnet *usbdev)  			return;  		} -		netdev_for_each_mc_addr(mclist, usbdev->net) +		netdev_for_each_mc_addr(ha, usbdev->net)  			memcpy(mc_addrs + i++ * ETH_ALEN, -			       mclist->dmi_addr, ETH_ALEN); +			       ha->addr, ETH_ALEN);  	}  	netif_addr_unlock_bh(usbdev->net); @@ -1569,6 +1619,194 @@ set_filter:  		   le32_to_cpu(filter), ret);  } +#ifdef DEBUG +static void debug_print_pmkids(struct usbnet *usbdev, +				struct ndis_80211_pmkid *pmkids, +				const char *func_str) +{ +	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); +	int i, len, count, max_pmkids, entry_len; + +	max_pmkids = priv->wdev.wiphy->max_num_pmkids; +	len = le32_to_cpu(pmkids->length); +	count = le32_to_cpu(pmkids->bssid_info_count); + +	entry_len = (count > 0) ? (len - sizeof(*pmkids)) / count : -1; + +	netdev_dbg(usbdev->net, "%s(): %d PMKIDs (data len: %d, entry len: " +				"%d)\n", func_str, count, len, entry_len); + +	if (count > max_pmkids) +		count = max_pmkids; + +	for (i = 0; i < count; i++) { +		u32 *tmp = (u32 *)pmkids->bssid_info[i].pmkid; + +		netdev_dbg(usbdev->net, "%s():  bssid: %pM, " +				"pmkid: %08X:%08X:%08X:%08X\n", +				func_str, pmkids->bssid_info[i].bssid, +				cpu_to_be32(tmp[0]), cpu_to_be32(tmp[1]), +				cpu_to_be32(tmp[2]), cpu_to_be32(tmp[3])); +	} +} +#else +static void debug_print_pmkids(struct usbnet *usbdev, +				struct ndis_80211_pmkid *pmkids, +				const char *func_str) +{ +	return; +} +#endif + +static struct ndis_80211_pmkid *get_device_pmkids(struct usbnet *usbdev) +{ +	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); +	struct ndis_80211_pmkid *pmkids; +	int len, ret, max_pmkids; + +	max_pmkids = priv->wdev.wiphy->max_num_pmkids; +	len = sizeof(*pmkids) + max_pmkids * sizeof(pmkids->bssid_info[0]); + +	pmkids = kzalloc(len, GFP_KERNEL); +	if (!pmkids) +		return ERR_PTR(-ENOMEM); + +	pmkids->length = cpu_to_le32(len); +	pmkids->bssid_info_count = cpu_to_le32(max_pmkids); + +	ret = rndis_query_oid(usbdev, OID_802_11_PMKID, pmkids, &len); +	if (ret < 0) { +		netdev_dbg(usbdev->net, "%s(): OID_802_11_PMKID(%d, %d)" +				" -> %d\n", __func__, len, max_pmkids, ret); + +		kfree(pmkids); +		return ERR_PTR(ret); +	} + +	if (le32_to_cpu(pmkids->bssid_info_count) > max_pmkids) +		pmkids->bssid_info_count = cpu_to_le32(max_pmkids); + +	debug_print_pmkids(usbdev, pmkids, __func__); + +	return pmkids; +} + +static int set_device_pmkids(struct usbnet *usbdev, +				struct ndis_80211_pmkid *pmkids) +{ +	int ret, len, num_pmkids; + +	num_pmkids = le32_to_cpu(pmkids->bssid_info_count); +	len = sizeof(*pmkids) + num_pmkids * sizeof(pmkids->bssid_info[0]); +	pmkids->length = cpu_to_le32(len); + +	debug_print_pmkids(usbdev, pmkids, __func__); + +	ret = rndis_set_oid(usbdev, OID_802_11_PMKID, pmkids, +						le32_to_cpu(pmkids->length)); +	if (ret < 0) { +		netdev_dbg(usbdev->net, "%s(): OID_802_11_PMKID(%d, %d) -> %d" +				"\n", __func__, len, num_pmkids, ret); +	} + +	kfree(pmkids); +	return ret; +} + +static struct ndis_80211_pmkid *remove_pmkid(struct usbnet *usbdev, +						struct ndis_80211_pmkid *pmkids, +						struct cfg80211_pmksa *pmksa, +						int max_pmkids) +{ +	int i, len, count, newlen, err; + +	len = le32_to_cpu(pmkids->length); +	count = le32_to_cpu(pmkids->bssid_info_count); + +	if (count > max_pmkids) +		count = max_pmkids; + +	for (i = 0; i < count; i++) +		if (!compare_ether_addr(pmkids->bssid_info[i].bssid, +							pmksa->bssid)) +			break; + +	/* pmkid not found */ +	if (i == count) { +		netdev_dbg(usbdev->net, "%s(): bssid not found (%pM)\n", +					__func__, pmksa->bssid); +		err = -ENOENT; +		goto error; +	} + +	for (; i + 1 < count; i++) +		pmkids->bssid_info[i] = pmkids->bssid_info[i + 1]; + +	count--; +	newlen = sizeof(*pmkids) + count * sizeof(pmkids->bssid_info[0]); + +	pmkids->length = cpu_to_le32(newlen); +	pmkids->bssid_info_count = cpu_to_le32(count); + +	return pmkids; +error: +	kfree(pmkids); +	return ERR_PTR(err); +} + +static struct ndis_80211_pmkid *update_pmkid(struct usbnet *usbdev, +						struct ndis_80211_pmkid *pmkids, +						struct cfg80211_pmksa *pmksa, +						int max_pmkids) +{ +	int i, err, len, count, newlen; + +	len = le32_to_cpu(pmkids->length); +	count = le32_to_cpu(pmkids->bssid_info_count); + +	if (count > max_pmkids) +		count = max_pmkids; + +	/* update with new pmkid */ +	for (i = 0; i < count; i++) { +		if (compare_ether_addr(pmkids->bssid_info[i].bssid, +							pmksa->bssid)) +			continue; + +		memcpy(pmkids->bssid_info[i].pmkid, pmksa->pmkid, +								WLAN_PMKID_LEN); + +		return pmkids; +	} + +	/* out of space, return error */ +	if (i == max_pmkids) { +		netdev_dbg(usbdev->net, "%s(): out of space\n", __func__); +		err = -ENOSPC; +		goto error; +	} + +	/* add new pmkid */ +	newlen = sizeof(*pmkids) + (count + 1) * sizeof(pmkids->bssid_info[0]); + +	pmkids = krealloc(pmkids, newlen, GFP_KERNEL); +	if (!pmkids) { +		err = -ENOMEM; +		goto error; +	} + +	pmkids->length = cpu_to_le32(newlen); +	pmkids->bssid_info_count = cpu_to_le32(count + 1); + +	memcpy(pmkids->bssid_info[count].bssid, pmksa->bssid, ETH_ALEN); +	memcpy(pmkids->bssid_info[count].pmkid, pmksa->pmkid, WLAN_PMKID_LEN); + +	return pmkids; +error: +	kfree(pmkids); +	return ERR_PTR(err); +} +  /*   * cfg80211 ops   */ @@ -2179,6 +2417,78 @@ static int rndis_dump_station(struct wiphy *wiphy, struct net_device *dev,  	return 0;  } +static int rndis_set_pmksa(struct wiphy *wiphy, struct net_device *netdev, +				struct cfg80211_pmksa *pmksa) +{ +	struct rndis_wlan_private *priv = wiphy_priv(wiphy); +	struct usbnet *usbdev = priv->usbdev; +	struct ndis_80211_pmkid *pmkids; +	u32 *tmp = (u32 *)pmksa->pmkid; + +	netdev_dbg(usbdev->net, "%s(%pM, %08X:%08X:%08X:%08X)\n", __func__, +			pmksa->bssid, +			cpu_to_be32(tmp[0]), cpu_to_be32(tmp[1]), +			cpu_to_be32(tmp[2]), cpu_to_be32(tmp[3])); + +	pmkids = get_device_pmkids(usbdev); +	if (IS_ERR(pmkids)) { +		/* couldn't read PMKID cache from device */ +		return PTR_ERR(pmkids); +	} + +	pmkids = update_pmkid(usbdev, pmkids, pmksa, wiphy->max_num_pmkids); +	if (IS_ERR(pmkids)) { +		/* not found, list full, etc */ +		return PTR_ERR(pmkids); +	} + +	return set_device_pmkids(usbdev, pmkids); +} + +static int rndis_del_pmksa(struct wiphy *wiphy, struct net_device *netdev, +				struct cfg80211_pmksa *pmksa) +{ +	struct rndis_wlan_private *priv = wiphy_priv(wiphy); +	struct usbnet *usbdev = priv->usbdev; +	struct ndis_80211_pmkid *pmkids; +	u32 *tmp = (u32 *)pmksa->pmkid; + +	netdev_dbg(usbdev->net, "%s(%pM, %08X:%08X:%08X:%08X)\n", __func__, +			pmksa->bssid, +			cpu_to_be32(tmp[0]), cpu_to_be32(tmp[1]), +			cpu_to_be32(tmp[2]), cpu_to_be32(tmp[3])); + +	pmkids = get_device_pmkids(usbdev); +	if (IS_ERR(pmkids)) { +		/* Couldn't read PMKID cache from device */ +		return PTR_ERR(pmkids); +	} + +	pmkids = remove_pmkid(usbdev, pmkids, pmksa, wiphy->max_num_pmkids); +	if (IS_ERR(pmkids)) { +		/* not found, etc */ +		return PTR_ERR(pmkids); +	} + +	return set_device_pmkids(usbdev, pmkids); +} + +static int rndis_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev) +{ +	struct rndis_wlan_private *priv = wiphy_priv(wiphy); +	struct usbnet *usbdev = priv->usbdev; +	struct ndis_80211_pmkid pmkid; + +	netdev_dbg(usbdev->net, "%s()\n", __func__); + +	memset(&pmkid, 0, sizeof(pmkid)); + +	pmkid.length = cpu_to_le32(sizeof(pmkid)); +	pmkid.bssid_info_count = cpu_to_le32(0); + +	return rndis_set_oid(usbdev, OID_802_11_PMKID, &pmkid, sizeof(pmkid)); +} +  /*   * workers, indication handlers, device poller   */ @@ -2523,12 +2833,14 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen)  	}  } -static int rndis_wlan_get_caps(struct usbnet *usbdev) +static int rndis_wlan_get_caps(struct usbnet *usbdev, struct wiphy *wiphy)  {  	struct {  		__le32	num_items;  		__le32	items[8];  	} networks_supported; +	struct ndis_80211_capability *caps; +	u8 caps_buf[sizeof(*caps) + sizeof(caps->auth_encr_pair) * 16];  	int len, retval, i, n;  	struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); @@ -2556,6 +2868,21 @@ static int rndis_wlan_get_caps(struct usbnet *usbdev)  		}  	} +	/* get device 802.11 capabilities, number of PMKIDs */ +	caps = (struct ndis_80211_capability *)caps_buf; +	len = sizeof(caps_buf); +	retval = rndis_query_oid(usbdev, OID_802_11_CAPABILITY, caps, &len); +	if (retval >= 0) { +		netdev_dbg(usbdev->net, "OID_802_11_CAPABILITY -> len %d, " +				"ver %d, pmkids %d, auth-encr-pairs %d\n", +				le32_to_cpu(caps->length), +				le32_to_cpu(caps->version), +				le32_to_cpu(caps->num_pmkids), +				le32_to_cpu(caps->num_auth_encr_pair)); +		wiphy->max_num_pmkids = le32_to_cpu(caps->num_pmkids); +	} else +		wiphy->max_num_pmkids = 0; +  	return retval;  } @@ -2803,7 +3130,7 @@ static int rndis_wlan_bind(struct usbnet *usbdev, struct usb_interface *intf)  	wiphy->max_scan_ssids = 1;  	/* TODO: fill-out band/encr information based on priv->caps */ -	rndis_wlan_get_caps(usbdev); +	rndis_wlan_get_caps(usbdev, wiphy);  	memcpy(priv->channels, rndis_channels, sizeof(rndis_channels));  	memcpy(priv->rates, rndis_rates, sizeof(rndis_rates)); @@ -2863,9 +3190,6 @@ static void rndis_wlan_unbind(struct usbnet *usbdev, struct usb_interface *intf)  	flush_workqueue(priv->workqueue);  	destroy_workqueue(priv->workqueue); -	if (priv && priv->wpa_ie_len) -		kfree(priv->wpa_ie); -  	rndis_unbind(usbdev, intf);  	wiphy_unregister(priv->wdev.wiphy);  |