|
@@ -356,8 +356,11 @@ struct rndis_wext_private {
|
|
|
|
|
|
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;
|
|
@@ -413,8 +416,12 @@ static int rndis_change_virtual_intf(struct wiphy *wiphy, int ifindex,
|
|
|
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);
|
|
|
+
|
|
|
struct cfg80211_ops rndis_config_ops = {
|
|
|
.change_virtual_intf = rndis_change_virtual_intf,
|
|
|
+ .scan = rndis_scan,
|
|
|
};
|
|
|
|
|
|
void *rndis_wiphy_privid = &rndis_wiphy_privid;
|
|
@@ -1164,6 +1171,142 @@ static int rndis_change_virtual_intf(struct wiphy *wiphy, int ifindex,
|
|
|
return set_infra_mode(usbdev, mode);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+#define SCAN_DELAY_JIFFIES (HZ)
|
|
|
+static int rndis_scan(struct wiphy *wiphy, struct net_device *dev,
|
|
|
+ struct cfg80211_scan_request *request)
|
|
|
+{
|
|
|
+ struct usbnet *usbdev = netdev_priv(dev);
|
|
|
+ struct rndis_wext_private *priv = get_rndis_wext_priv(usbdev);
|
|
|
+ int ret;
|
|
|
+ __le32 tmp;
|
|
|
+
|
|
|
+ devdbg(usbdev, "cfg80211.scan");
|
|
|
+
|
|
|
+ if (!request)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (priv->scan_request && priv->scan_request != request)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ priv->scan_request = request;
|
|
|
+
|
|
|
+ tmp = cpu_to_le32(1);
|
|
|
+ ret = rndis_set_oid(usbdev, OID_802_11_BSSID_LIST_SCAN, &tmp,
|
|
|
+ sizeof(tmp));
|
|
|
+ if (ret == 0) {
|
|
|
+ /* Wait before retrieving scan results from device */
|
|
|
+ queue_delayed_work(priv->workqueue, &priv->scan_work,
|
|
|
+ SCAN_DELAY_JIFFIES);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static struct cfg80211_bss *rndis_bss_info_update(struct usbnet *usbdev,
|
|
|
+ struct ndis_80211_bssid_ex *bssid)
|
|
|
+{
|
|
|
+ struct rndis_wext_private *priv = get_rndis_wext_priv(usbdev);
|
|
|
+ struct ieee80211_channel *channel;
|
|
|
+ s32 signal;
|
|
|
+ u64 timestamp;
|
|
|
+ u16 capability;
|
|
|
+ u16 beacon_interval;
|
|
|
+ struct ndis_80211_fixed_ies *fixed;
|
|
|
+ int ie_len, bssid_len;
|
|
|
+ u8 *ie;
|
|
|
+
|
|
|
+ /* parse bssid structure */
|
|
|
+ bssid_len = le32_to_cpu(bssid->length);
|
|
|
+
|
|
|
+ if (bssid_len < sizeof(struct ndis_80211_bssid_ex) +
|
|
|
+ sizeof(struct ndis_80211_fixed_ies))
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ fixed = (struct ndis_80211_fixed_ies *)bssid->ies;
|
|
|
+
|
|
|
+ ie = (void *)(bssid->ies + sizeof(struct ndis_80211_fixed_ies));
|
|
|
+ ie_len = min(bssid_len - (int)sizeof(*bssid),
|
|
|
+ (int)le32_to_cpu(bssid->ie_length));
|
|
|
+ ie_len -= sizeof(struct ndis_80211_fixed_ies);
|
|
|
+ if (ie_len < 0)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ /* extract data for cfg80211_inform_bss */
|
|
|
+ channel = ieee80211_get_channel(priv->wdev.wiphy,
|
|
|
+ KHZ_TO_MHZ(le32_to_cpu(bssid->config.ds_config)));
|
|
|
+ if (!channel)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ signal = level_to_qual(le32_to_cpu(bssid->rssi));
|
|
|
+ timestamp = le64_to_cpu(*(__le64 *)fixed->timestamp);
|
|
|
+ capability = le16_to_cpu(fixed->capabilities);
|
|
|
+ beacon_interval = le16_to_cpu(fixed->beacon_interval);
|
|
|
+
|
|
|
+ return cfg80211_inform_bss(priv->wdev.wiphy, channel, bssid->mac,
|
|
|
+ timestamp, capability, beacon_interval, ie, ie_len, signal,
|
|
|
+ GFP_KERNEL);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static int rndis_check_bssid_list(struct usbnet *usbdev)
|
|
|
+{
|
|
|
+ void *buf = NULL;
|
|
|
+ struct ndis_80211_bssid_list_ex *bssid_list;
|
|
|
+ struct ndis_80211_bssid_ex *bssid;
|
|
|
+ int ret = -EINVAL, len, count, bssid_len;
|
|
|
+
|
|
|
+ devdbg(usbdev, "check_bssid_list");
|
|
|
+
|
|
|
+ len = CONTROL_BUFFER_SIZE;
|
|
|
+ buf = kmalloc(len, GFP_KERNEL);
|
|
|
+ if (!buf) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = rndis_query_oid(usbdev, OID_802_11_BSSID_LIST, buf, &len);
|
|
|
+ if (ret != 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ bssid_list = buf;
|
|
|
+ bssid = bssid_list->bssid;
|
|
|
+ bssid_len = le32_to_cpu(bssid->length);
|
|
|
+ count = le32_to_cpu(bssid_list->num_items);
|
|
|
+ devdbg(usbdev, "check_bssid_list: %d BSSIDs found", count);
|
|
|
+
|
|
|
+ while (count && ((void *)bssid + bssid_len) <= (buf + len)) {
|
|
|
+ rndis_bss_info_update(usbdev, bssid);
|
|
|
+
|
|
|
+ bssid = (void *)bssid + bssid_len;
|
|
|
+ bssid_len = le32_to_cpu(bssid->length);
|
|
|
+ count--;
|
|
|
+ }
|
|
|
+
|
|
|
+out:
|
|
|
+ kfree(buf);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static void rndis_get_scan_results(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct rndis_wext_private *priv =
|
|
|
+ container_of(work, struct rndis_wext_private, scan_work.work);
|
|
|
+ struct usbnet *usbdev = priv->usbdev;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ devdbg(usbdev, "get_scan_results");
|
|
|
+
|
|
|
+ ret = rndis_check_bssid_list(usbdev);
|
|
|
+
|
|
|
+ cfg80211_scan_done(priv->scan_request, ret < 0);
|
|
|
+
|
|
|
+ priv->scan_request = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/*
|
|
|
* wireless extension handlers
|
|
|
*/
|
|
@@ -1531,198 +1674,6 @@ static int rndis_iw_set_encode_ext(struct net_device *dev,
|
|
|
}
|
|
|
|
|
|
|
|
|
-static int rndis_iw_set_scan(struct net_device *dev,
|
|
|
- struct iw_request_info *info, union iwreq_data *wrqu, char *extra)
|
|
|
-{
|
|
|
- struct usbnet *usbdev = netdev_priv(dev);
|
|
|
- union iwreq_data evt;
|
|
|
- int ret = -EINVAL;
|
|
|
- __le32 tmp;
|
|
|
-
|
|
|
- devdbg(usbdev, "SIOCSIWSCAN");
|
|
|
-
|
|
|
- if (wrqu->data.flags == 0) {
|
|
|
- tmp = cpu_to_le32(1);
|
|
|
- ret = rndis_set_oid(usbdev, OID_802_11_BSSID_LIST_SCAN, &tmp,
|
|
|
- sizeof(tmp));
|
|
|
- evt.data.flags = 0;
|
|
|
- evt.data.length = 0;
|
|
|
- wireless_send_event(dev, SIOCGIWSCAN, &evt, NULL);
|
|
|
- }
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-static char *rndis_translate_scan(struct net_device *dev,
|
|
|
- struct iw_request_info *info, char *cev,
|
|
|
- char *end_buf,
|
|
|
- struct ndis_80211_bssid_ex *bssid)
|
|
|
-{
|
|
|
- struct usbnet *usbdev = netdev_priv(dev);
|
|
|
- u8 *ie;
|
|
|
- char *current_val;
|
|
|
- int bssid_len, ie_len, i;
|
|
|
- u32 beacon, atim;
|
|
|
- struct iw_event iwe;
|
|
|
- unsigned char sbuf[32];
|
|
|
-
|
|
|
- bssid_len = le32_to_cpu(bssid->length);
|
|
|
-
|
|
|
- devdbg(usbdev, "BSSID %pM", bssid->mac);
|
|
|
- iwe.cmd = SIOCGIWAP;
|
|
|
- iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
|
|
|
- memcpy(iwe.u.ap_addr.sa_data, bssid->mac, ETH_ALEN);
|
|
|
- cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_ADDR_LEN);
|
|
|
-
|
|
|
- devdbg(usbdev, "SSID(%d) %s", le32_to_cpu(bssid->ssid.length),
|
|
|
- bssid->ssid.essid);
|
|
|
- iwe.cmd = SIOCGIWESSID;
|
|
|
- iwe.u.essid.length = le32_to_cpu(bssid->ssid.length);
|
|
|
- iwe.u.essid.flags = 1;
|
|
|
- cev = iwe_stream_add_point(info, cev, end_buf, &iwe, bssid->ssid.essid);
|
|
|
-
|
|
|
- devdbg(usbdev, "MODE %d", le32_to_cpu(bssid->net_infra));
|
|
|
- iwe.cmd = SIOCGIWMODE;
|
|
|
- switch (le32_to_cpu(bssid->net_infra)) {
|
|
|
- case ndis_80211_infra_adhoc:
|
|
|
- iwe.u.mode = IW_MODE_ADHOC;
|
|
|
- break;
|
|
|
- case ndis_80211_infra_infra:
|
|
|
- iwe.u.mode = IW_MODE_INFRA;
|
|
|
- break;
|
|
|
- /*case ndis_80211_infra_auto_unknown:*/
|
|
|
- default:
|
|
|
- iwe.u.mode = IW_MODE_AUTO;
|
|
|
- break;
|
|
|
- }
|
|
|
- cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_UINT_LEN);
|
|
|
-
|
|
|
- devdbg(usbdev, "FREQ %d kHz", le32_to_cpu(bssid->config.ds_config));
|
|
|
- iwe.cmd = SIOCGIWFREQ;
|
|
|
- dsconfig_to_freq(le32_to_cpu(bssid->config.ds_config), &iwe.u.freq);
|
|
|
- cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_FREQ_LEN);
|
|
|
-
|
|
|
- devdbg(usbdev, "QUAL %d", le32_to_cpu(bssid->rssi));
|
|
|
- iwe.cmd = IWEVQUAL;
|
|
|
- iwe.u.qual.qual = level_to_qual(le32_to_cpu(bssid->rssi));
|
|
|
- iwe.u.qual.level = level_to_qual(le32_to_cpu(bssid->rssi));
|
|
|
- iwe.u.qual.updated = IW_QUAL_QUAL_UPDATED
|
|
|
- | IW_QUAL_LEVEL_UPDATED
|
|
|
- | IW_QUAL_NOISE_INVALID;
|
|
|
- cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_QUAL_LEN);
|
|
|
-
|
|
|
- devdbg(usbdev, "ENCODE %d", le32_to_cpu(bssid->privacy));
|
|
|
- iwe.cmd = SIOCGIWENCODE;
|
|
|
- iwe.u.data.length = 0;
|
|
|
- if (le32_to_cpu(bssid->privacy) == ndis_80211_priv_accept_all)
|
|
|
- iwe.u.data.flags = IW_ENCODE_DISABLED;
|
|
|
- else
|
|
|
- iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
|
|
|
-
|
|
|
- cev = iwe_stream_add_point(info, cev, end_buf, &iwe, NULL);
|
|
|
-
|
|
|
- devdbg(usbdev, "RATES:");
|
|
|
- current_val = cev + iwe_stream_lcp_len(info);
|
|
|
- iwe.cmd = SIOCGIWRATE;
|
|
|
- for (i = 0; i < sizeof(bssid->rates); i++) {
|
|
|
- if (bssid->rates[i] & 0x7f) {
|
|
|
- iwe.u.bitrate.value =
|
|
|
- ((bssid->rates[i] & 0x7f) *
|
|
|
- 500000);
|
|
|
- devdbg(usbdev, " %d", iwe.u.bitrate.value);
|
|
|
- current_val = iwe_stream_add_value(info, cev,
|
|
|
- current_val, end_buf, &iwe,
|
|
|
- IW_EV_PARAM_LEN);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ((current_val - cev) > iwe_stream_lcp_len(info))
|
|
|
- cev = current_val;
|
|
|
-
|
|
|
- beacon = le32_to_cpu(bssid->config.beacon_period);
|
|
|
- devdbg(usbdev, "BCN_INT %d", beacon);
|
|
|
- iwe.cmd = IWEVCUSTOM;
|
|
|
- snprintf(sbuf, sizeof(sbuf), "bcn_int=%d", beacon);
|
|
|
- iwe.u.data.length = strlen(sbuf);
|
|
|
- cev = iwe_stream_add_point(info, cev, end_buf, &iwe, sbuf);
|
|
|
-
|
|
|
- atim = le32_to_cpu(bssid->config.atim_window);
|
|
|
- devdbg(usbdev, "ATIM %d", atim);
|
|
|
- iwe.cmd = IWEVCUSTOM;
|
|
|
- snprintf(sbuf, sizeof(sbuf), "atim=%u", atim);
|
|
|
- iwe.u.data.length = strlen(sbuf);
|
|
|
- cev = iwe_stream_add_point(info, cev, end_buf, &iwe, sbuf);
|
|
|
-
|
|
|
- ie = (void *)(bssid->ies + sizeof(struct ndis_80211_fixed_ies));
|
|
|
- ie_len = min(bssid_len - (int)sizeof(*bssid),
|
|
|
- (int)le32_to_cpu(bssid->ie_length));
|
|
|
- ie_len -= sizeof(struct ndis_80211_fixed_ies);
|
|
|
- while (ie_len >= 2 && 2 + ie[1] <= ie_len) {
|
|
|
- if ((ie[0] == WLAN_EID_GENERIC && ie[1] >= 4 &&
|
|
|
- memcmp(ie + 2, "\x00\x50\xf2\x01", 4) == 0) ||
|
|
|
- ie[0] == WLAN_EID_RSN) {
|
|
|
- devdbg(usbdev, "IE: WPA%d",
|
|
|
- (ie[0] == WLAN_EID_RSN) ? 2 : 1);
|
|
|
- iwe.cmd = IWEVGENIE;
|
|
|
- /* arbitrary cut-off at 64 */
|
|
|
- iwe.u.data.length = min(ie[1] + 2, 64);
|
|
|
- cev = iwe_stream_add_point(info, cev, end_buf, &iwe, ie);
|
|
|
- }
|
|
|
-
|
|
|
- ie_len -= 2 + ie[1];
|
|
|
- ie += 2 + ie[1];
|
|
|
- }
|
|
|
-
|
|
|
- return cev;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-static int rndis_iw_get_scan(struct net_device *dev,
|
|
|
- struct iw_request_info *info, union iwreq_data *wrqu, char *extra)
|
|
|
-{
|
|
|
- struct usbnet *usbdev = netdev_priv(dev);
|
|
|
- void *buf = NULL;
|
|
|
- char *cev = extra;
|
|
|
- struct ndis_80211_bssid_list_ex *bssid_list;
|
|
|
- struct ndis_80211_bssid_ex *bssid;
|
|
|
- int ret = -EINVAL, len, count, bssid_len;
|
|
|
-
|
|
|
- devdbg(usbdev, "SIOCGIWSCAN");
|
|
|
-
|
|
|
- len = CONTROL_BUFFER_SIZE;
|
|
|
- buf = kmalloc(len, GFP_KERNEL);
|
|
|
- if (!buf) {
|
|
|
- ret = -ENOMEM;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- ret = rndis_query_oid(usbdev, OID_802_11_BSSID_LIST, buf, &len);
|
|
|
-
|
|
|
- if (ret != 0)
|
|
|
- goto out;
|
|
|
-
|
|
|
- bssid_list = buf;
|
|
|
- bssid = bssid_list->bssid;
|
|
|
- bssid_len = le32_to_cpu(bssid->length);
|
|
|
- count = le32_to_cpu(bssid_list->num_items);
|
|
|
- devdbg(usbdev, "SIOCGIWSCAN: %d BSSIDs found", count);
|
|
|
-
|
|
|
- while (count && ((void *)bssid + bssid_len) <= (buf + len)) {
|
|
|
- cev = rndis_translate_scan(dev, info, cev,
|
|
|
- extra + IW_SCAN_MAX_DATA, bssid);
|
|
|
- bssid = (void *)bssid + bssid_len;
|
|
|
- bssid_len = le32_to_cpu(bssid->length);
|
|
|
- count--;
|
|
|
- }
|
|
|
-
|
|
|
-out:
|
|
|
- wrqu->data.length = cev - extra;
|
|
|
- wrqu->data.flags = 0;
|
|
|
- kfree(buf);
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
static int rndis_iw_set_genie(struct net_device *dev,
|
|
|
struct iw_request_info *info, union iwreq_data *wrqu, char *extra)
|
|
|
{
|
|
@@ -2085,8 +2036,8 @@ static const iw_handler rndis_iw_handler[] =
|
|
|
IW_IOCTL(SIOCGIWRANGE) = (iw_handler) cfg80211_wext_giwrange,
|
|
|
IW_IOCTL(SIOCSIWAP) = rndis_iw_set_bssid,
|
|
|
IW_IOCTL(SIOCGIWAP) = rndis_iw_get_bssid,
|
|
|
- IW_IOCTL(SIOCSIWSCAN) = rndis_iw_set_scan,
|
|
|
- IW_IOCTL(SIOCGIWSCAN) = rndis_iw_get_scan,
|
|
|
+ IW_IOCTL(SIOCSIWSCAN) = (iw_handler) cfg80211_wext_siwscan,
|
|
|
+ IW_IOCTL(SIOCGIWSCAN) = (iw_handler) cfg80211_wext_giwscan,
|
|
|
IW_IOCTL(SIOCSIWESSID) = rndis_iw_set_essid,
|
|
|
IW_IOCTL(SIOCGIWESSID) = rndis_iw_get_essid,
|
|
|
IW_IOCTL(SIOCSIWNICKN) = rndis_iw_set_nick,
|
|
@@ -2547,6 +2498,7 @@ static int rndis_wext_bind(struct usbnet *usbdev, struct usb_interface *intf)
|
|
|
INIT_DELAYED_WORK(&priv->stats_work, rndis_update_wireless_stats);
|
|
|
queue_delayed_work(priv->workqueue, &priv->stats_work,
|
|
|
round_jiffies_relative(STATS_UPDATE_JIFFIES));
|
|
|
+ INIT_DELAYED_WORK(&priv->scan_work, rndis_get_scan_results);
|
|
|
INIT_WORK(&priv->work, rndis_wext_worker);
|
|
|
|
|
|
return 0;
|
|
@@ -2565,6 +2517,7 @@ static void rndis_wext_unbind(struct usbnet *usbdev, struct usb_interface *intf)
|
|
|
disassociate(usbdev, 0);
|
|
|
|
|
|
cancel_delayed_work_sync(&priv->stats_work);
|
|
|
+ cancel_delayed_work_sync(&priv->scan_work);
|
|
|
cancel_work_sync(&priv->work);
|
|
|
flush_workqueue(priv->workqueue);
|
|
|
destroy_workqueue(priv->workqueue);
|