diff options
-rw-r--r-- | drivers/net/wireless/rndis_wlan.c | 341 |
1 files changed, 147 insertions, 194 deletions
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 4252903cf89a..239e6a14eae2 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c | |||
@@ -356,8 +356,11 @@ struct rndis_wext_private { | |||
356 | 356 | ||
357 | struct wireless_dev wdev; | 357 | struct wireless_dev wdev; |
358 | 358 | ||
359 | struct cfg80211_scan_request *scan_request; | ||
360 | |||
359 | struct workqueue_struct *workqueue; | 361 | struct workqueue_struct *workqueue; |
360 | struct delayed_work stats_work; | 362 | struct delayed_work stats_work; |
363 | struct delayed_work scan_work; | ||
361 | struct work_struct work; | 364 | struct work_struct work; |
362 | struct mutex command_lock; | 365 | struct mutex command_lock; |
363 | spinlock_t stats_lock; | 366 | spinlock_t stats_lock; |
@@ -413,8 +416,12 @@ static int rndis_change_virtual_intf(struct wiphy *wiphy, int ifindex, | |||
413 | enum nl80211_iftype type, u32 *flags, | 416 | enum nl80211_iftype type, u32 *flags, |
414 | struct vif_params *params); | 417 | struct vif_params *params); |
415 | 418 | ||
419 | static int rndis_scan(struct wiphy *wiphy, struct net_device *dev, | ||
420 | struct cfg80211_scan_request *request); | ||
421 | |||
416 | struct cfg80211_ops rndis_config_ops = { | 422 | struct cfg80211_ops rndis_config_ops = { |
417 | .change_virtual_intf = rndis_change_virtual_intf, | 423 | .change_virtual_intf = rndis_change_virtual_intf, |
424 | .scan = rndis_scan, | ||
418 | }; | 425 | }; |
419 | 426 | ||
420 | void *rndis_wiphy_privid = &rndis_wiphy_privid; | 427 | void *rndis_wiphy_privid = &rndis_wiphy_privid; |
@@ -1164,6 +1171,142 @@ static int rndis_change_virtual_intf(struct wiphy *wiphy, int ifindex, | |||
1164 | return set_infra_mode(usbdev, mode); | 1171 | return set_infra_mode(usbdev, mode); |
1165 | } | 1172 | } |
1166 | 1173 | ||
1174 | |||
1175 | #define SCAN_DELAY_JIFFIES (HZ) | ||
1176 | static int rndis_scan(struct wiphy *wiphy, struct net_device *dev, | ||
1177 | struct cfg80211_scan_request *request) | ||
1178 | { | ||
1179 | struct usbnet *usbdev = netdev_priv(dev); | ||
1180 | struct rndis_wext_private *priv = get_rndis_wext_priv(usbdev); | ||
1181 | int ret; | ||
1182 | __le32 tmp; | ||
1183 | |||
1184 | devdbg(usbdev, "cfg80211.scan"); | ||
1185 | |||
1186 | if (!request) | ||
1187 | return -EINVAL; | ||
1188 | |||
1189 | if (priv->scan_request && priv->scan_request != request) | ||
1190 | return -EBUSY; | ||
1191 | |||
1192 | priv->scan_request = request; | ||
1193 | |||
1194 | tmp = cpu_to_le32(1); | ||
1195 | ret = rndis_set_oid(usbdev, OID_802_11_BSSID_LIST_SCAN, &tmp, | ||
1196 | sizeof(tmp)); | ||
1197 | if (ret == 0) { | ||
1198 | /* Wait before retrieving scan results from device */ | ||
1199 | queue_delayed_work(priv->workqueue, &priv->scan_work, | ||
1200 | SCAN_DELAY_JIFFIES); | ||
1201 | } | ||
1202 | |||
1203 | return ret; | ||
1204 | } | ||
1205 | |||
1206 | |||
1207 | static struct cfg80211_bss *rndis_bss_info_update(struct usbnet *usbdev, | ||
1208 | struct ndis_80211_bssid_ex *bssid) | ||
1209 | { | ||
1210 | struct rndis_wext_private *priv = get_rndis_wext_priv(usbdev); | ||
1211 | struct ieee80211_channel *channel; | ||
1212 | s32 signal; | ||
1213 | u64 timestamp; | ||
1214 | u16 capability; | ||
1215 | u16 beacon_interval; | ||
1216 | struct ndis_80211_fixed_ies *fixed; | ||
1217 | int ie_len, bssid_len; | ||
1218 | u8 *ie; | ||
1219 | |||
1220 | /* parse bssid structure */ | ||
1221 | bssid_len = le32_to_cpu(bssid->length); | ||
1222 | |||
1223 | if (bssid_len < sizeof(struct ndis_80211_bssid_ex) + | ||
1224 | sizeof(struct ndis_80211_fixed_ies)) | ||
1225 | return NULL; | ||
1226 | |||
1227 | fixed = (struct ndis_80211_fixed_ies *)bssid->ies; | ||
1228 | |||
1229 | ie = (void *)(bssid->ies + sizeof(struct ndis_80211_fixed_ies)); | ||
1230 | ie_len = min(bssid_len - (int)sizeof(*bssid), | ||
1231 | (int)le32_to_cpu(bssid->ie_length)); | ||
1232 | ie_len -= sizeof(struct ndis_80211_fixed_ies); | ||
1233 | if (ie_len < 0) | ||
1234 | return NULL; | ||
1235 | |||
1236 | /* extract data for cfg80211_inform_bss */ | ||
1237 | channel = ieee80211_get_channel(priv->wdev.wiphy, | ||
1238 | KHZ_TO_MHZ(le32_to_cpu(bssid->config.ds_config))); | ||
1239 | if (!channel) | ||
1240 | return NULL; | ||
1241 | |||
1242 | signal = level_to_qual(le32_to_cpu(bssid->rssi)); | ||
1243 | timestamp = le64_to_cpu(*(__le64 *)fixed->timestamp); | ||
1244 | capability = le16_to_cpu(fixed->capabilities); | ||
1245 | beacon_interval = le16_to_cpu(fixed->beacon_interval); | ||
1246 | |||
1247 | return cfg80211_inform_bss(priv->wdev.wiphy, channel, bssid->mac, | ||
1248 | timestamp, capability, beacon_interval, ie, ie_len, signal, | ||
1249 | GFP_KERNEL); | ||
1250 | } | ||
1251 | |||
1252 | |||
1253 | static int rndis_check_bssid_list(struct usbnet *usbdev) | ||
1254 | { | ||
1255 | void *buf = NULL; | ||
1256 | struct ndis_80211_bssid_list_ex *bssid_list; | ||
1257 | struct ndis_80211_bssid_ex *bssid; | ||
1258 | int ret = -EINVAL, len, count, bssid_len; | ||
1259 | |||
1260 | devdbg(usbdev, "check_bssid_list"); | ||
1261 | |||
1262 | len = CONTROL_BUFFER_SIZE; | ||
1263 | buf = kmalloc(len, GFP_KERNEL); | ||
1264 | if (!buf) { | ||
1265 | ret = -ENOMEM; | ||
1266 | goto out; | ||
1267 | } | ||
1268 | |||
1269 | ret = rndis_query_oid(usbdev, OID_802_11_BSSID_LIST, buf, &len); | ||
1270 | if (ret != 0) | ||
1271 | goto out; | ||
1272 | |||
1273 | bssid_list = buf; | ||
1274 | bssid = bssid_list->bssid; | ||
1275 | bssid_len = le32_to_cpu(bssid->length); | ||
1276 | count = le32_to_cpu(bssid_list->num_items); | ||
1277 | devdbg(usbdev, "check_bssid_list: %d BSSIDs found", count); | ||
1278 | |||
1279 | while (count && ((void *)bssid + bssid_len) <= (buf + len)) { | ||
1280 | rndis_bss_info_update(usbdev, bssid); | ||
1281 | |||
1282 | bssid = (void *)bssid + bssid_len; | ||
1283 | bssid_len = le32_to_cpu(bssid->length); | ||
1284 | count--; | ||
1285 | } | ||
1286 | |||
1287 | out: | ||
1288 | kfree(buf); | ||
1289 | return ret; | ||
1290 | } | ||
1291 | |||
1292 | |||
1293 | static void rndis_get_scan_results(struct work_struct *work) | ||
1294 | { | ||
1295 | struct rndis_wext_private *priv = | ||
1296 | container_of(work, struct rndis_wext_private, scan_work.work); | ||
1297 | struct usbnet *usbdev = priv->usbdev; | ||
1298 | int ret; | ||
1299 | |||
1300 | devdbg(usbdev, "get_scan_results"); | ||
1301 | |||
1302 | ret = rndis_check_bssid_list(usbdev); | ||
1303 | |||
1304 | cfg80211_scan_done(priv->scan_request, ret < 0); | ||
1305 | |||
1306 | priv->scan_request = NULL; | ||
1307 | } | ||
1308 | |||
1309 | |||
1167 | /* | 1310 | /* |
1168 | * wireless extension handlers | 1311 | * wireless extension handlers |
1169 | */ | 1312 | */ |
@@ -1531,198 +1674,6 @@ static int rndis_iw_set_encode_ext(struct net_device *dev, | |||
1531 | } | 1674 | } |
1532 | 1675 | ||
1533 | 1676 | ||
1534 | static int rndis_iw_set_scan(struct net_device *dev, | ||
1535 | struct iw_request_info *info, union iwreq_data *wrqu, char *extra) | ||
1536 | { | ||
1537 | struct usbnet *usbdev = netdev_priv(dev); | ||
1538 | union iwreq_data evt; | ||
1539 | int ret = -EINVAL; | ||
1540 | __le32 tmp; | ||
1541 | |||
1542 | devdbg(usbdev, "SIOCSIWSCAN"); | ||
1543 | |||
1544 | if (wrqu->data.flags == 0) { | ||
1545 | tmp = cpu_to_le32(1); | ||
1546 | ret = rndis_set_oid(usbdev, OID_802_11_BSSID_LIST_SCAN, &tmp, | ||
1547 | sizeof(tmp)); | ||
1548 | evt.data.flags = 0; | ||
1549 | evt.data.length = 0; | ||
1550 | wireless_send_event(dev, SIOCGIWSCAN, &evt, NULL); | ||
1551 | } | ||
1552 | return ret; | ||
1553 | } | ||
1554 | |||
1555 | |||
1556 | static char *rndis_translate_scan(struct net_device *dev, | ||
1557 | struct iw_request_info *info, char *cev, | ||
1558 | char *end_buf, | ||
1559 | struct ndis_80211_bssid_ex *bssid) | ||
1560 | { | ||
1561 | struct usbnet *usbdev = netdev_priv(dev); | ||
1562 | u8 *ie; | ||
1563 | char *current_val; | ||
1564 | int bssid_len, ie_len, i; | ||
1565 | u32 beacon, atim; | ||
1566 | struct iw_event iwe; | ||
1567 | unsigned char sbuf[32]; | ||
1568 | |||
1569 | bssid_len = le32_to_cpu(bssid->length); | ||
1570 | |||
1571 | devdbg(usbdev, "BSSID %pM", bssid->mac); | ||
1572 | iwe.cmd = SIOCGIWAP; | ||
1573 | iwe.u.ap_addr.sa_family = ARPHRD_ETHER; | ||
1574 | memcpy(iwe.u.ap_addr.sa_data, bssid->mac, ETH_ALEN); | ||
1575 | cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_ADDR_LEN); | ||
1576 | |||
1577 | devdbg(usbdev, "SSID(%d) %s", le32_to_cpu(bssid->ssid.length), | ||
1578 | bssid->ssid.essid); | ||
1579 | iwe.cmd = SIOCGIWESSID; | ||
1580 | iwe.u.essid.length = le32_to_cpu(bssid->ssid.length); | ||
1581 | iwe.u.essid.flags = 1; | ||
1582 | cev = iwe_stream_add_point(info, cev, end_buf, &iwe, bssid->ssid.essid); | ||
1583 | |||
1584 | devdbg(usbdev, "MODE %d", le32_to_cpu(bssid->net_infra)); | ||
1585 | iwe.cmd = SIOCGIWMODE; | ||
1586 | switch (le32_to_cpu(bssid->net_infra)) { | ||
1587 | case ndis_80211_infra_adhoc: | ||
1588 | iwe.u.mode = IW_MODE_ADHOC; | ||
1589 | break; | ||
1590 | case ndis_80211_infra_infra: | ||
1591 | iwe.u.mode = IW_MODE_INFRA; | ||
1592 | break; | ||
1593 | /*case ndis_80211_infra_auto_unknown:*/ | ||
1594 | default: | ||
1595 | iwe.u.mode = IW_MODE_AUTO; | ||
1596 | break; | ||
1597 | } | ||
1598 | cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_UINT_LEN); | ||
1599 | |||
1600 | devdbg(usbdev, "FREQ %d kHz", le32_to_cpu(bssid->config.ds_config)); | ||
1601 | iwe.cmd = SIOCGIWFREQ; | ||
1602 | dsconfig_to_freq(le32_to_cpu(bssid->config.ds_config), &iwe.u.freq); | ||
1603 | cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_FREQ_LEN); | ||
1604 | |||
1605 | devdbg(usbdev, "QUAL %d", le32_to_cpu(bssid->rssi)); | ||
1606 | iwe.cmd = IWEVQUAL; | ||
1607 | iwe.u.qual.qual = level_to_qual(le32_to_cpu(bssid->rssi)); | ||
1608 | iwe.u.qual.level = level_to_qual(le32_to_cpu(bssid->rssi)); | ||
1609 | iwe.u.qual.updated = IW_QUAL_QUAL_UPDATED | ||
1610 | | IW_QUAL_LEVEL_UPDATED | ||
1611 | | IW_QUAL_NOISE_INVALID; | ||
1612 | cev = iwe_stream_add_event(info, cev, end_buf, &iwe, IW_EV_QUAL_LEN); | ||
1613 | |||
1614 | devdbg(usbdev, "ENCODE %d", le32_to_cpu(bssid->privacy)); | ||
1615 | iwe.cmd = SIOCGIWENCODE; | ||
1616 | iwe.u.data.length = 0; | ||
1617 | if (le32_to_cpu(bssid->privacy) == ndis_80211_priv_accept_all) | ||
1618 | iwe.u.data.flags = IW_ENCODE_DISABLED; | ||
1619 | else | ||
1620 | iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; | ||
1621 | |||
1622 | cev = iwe_stream_add_point(info, cev, end_buf, &iwe, NULL); | ||
1623 | |||
1624 | devdbg(usbdev, "RATES:"); | ||
1625 | current_val = cev + iwe_stream_lcp_len(info); | ||
1626 | iwe.cmd = SIOCGIWRATE; | ||
1627 | for (i = 0; i < sizeof(bssid->rates); i++) { | ||
1628 | if (bssid->rates[i] & 0x7f) { | ||
1629 | iwe.u.bitrate.value = | ||
1630 | ((bssid->rates[i] & 0x7f) * | ||
1631 | 500000); | ||
1632 | devdbg(usbdev, " %d", iwe.u.bitrate.value); | ||
1633 | current_val = iwe_stream_add_value(info, cev, | ||
1634 | current_val, end_buf, &iwe, | ||
1635 | IW_EV_PARAM_LEN); | ||
1636 | } | ||
1637 | } | ||
1638 | |||
1639 | if ((current_val - cev) > iwe_stream_lcp_len(info)) | ||
1640 | cev = current_val; | ||
1641 | |||
1642 | beacon = le32_to_cpu(bssid->config.beacon_period); | ||
1643 | devdbg(usbdev, "BCN_INT %d", beacon); | ||
1644 | iwe.cmd = IWEVCUSTOM; | ||
1645 | snprintf(sbuf, sizeof(sbuf), "bcn_int=%d", beacon); | ||
1646 | iwe.u.data.length = strlen(sbuf); | ||
1647 | cev = iwe_stream_add_point(info, cev, end_buf, &iwe, sbuf); | ||
1648 | |||
1649 | atim = le32_to_cpu(bssid->config.atim_window); | ||
1650 | devdbg(usbdev, "ATIM %d", atim); | ||
1651 | iwe.cmd = IWEVCUSTOM; | ||
1652 | snprintf(sbuf, sizeof(sbuf), "atim=%u", atim); | ||
1653 | iwe.u.data.length = strlen(sbuf); | ||
1654 | cev = iwe_stream_add_point(info, cev, end_buf, &iwe, sbuf); | ||
1655 | |||
1656 | ie = (void *)(bssid->ies + sizeof(struct ndis_80211_fixed_ies)); | ||
1657 | ie_len = min(bssid_len - (int)sizeof(*bssid), | ||
1658 | (int)le32_to_cpu(bssid->ie_length)); | ||
1659 | ie_len -= sizeof(struct ndis_80211_fixed_ies); | ||
1660 | while (ie_len >= 2 && 2 + ie[1] <= ie_len) { | ||
1661 | if ((ie[0] == WLAN_EID_GENERIC && ie[1] >= 4 && | ||
1662 | memcmp(ie + 2, "\x00\x50\xf2\x01", 4) == 0) || | ||
1663 | ie[0] == WLAN_EID_RSN) { | ||
1664 | devdbg(usbdev, "IE: WPA%d", | ||
1665 | (ie[0] == WLAN_EID_RSN) ? 2 : 1); | ||
1666 | iwe.cmd = IWEVGENIE; | ||
1667 | /* arbitrary cut-off at 64 */ | ||
1668 | iwe.u.data.length = min(ie[1] + 2, 64); | ||
1669 | cev = iwe_stream_add_point(info, cev, end_buf, &iwe, ie); | ||
1670 | } | ||
1671 | |||
1672 | ie_len -= 2 + ie[1]; | ||
1673 | ie += 2 + ie[1]; | ||
1674 | } | ||
1675 | |||
1676 | return cev; | ||
1677 | } | ||
1678 | |||
1679 | |||
1680 | static int rndis_iw_get_scan(struct net_device *dev, | ||
1681 | struct iw_request_info *info, union iwreq_data *wrqu, char *extra) | ||
1682 | { | ||
1683 | struct usbnet *usbdev = netdev_priv(dev); | ||
1684 | void *buf = NULL; | ||
1685 | char *cev = extra; | ||
1686 | struct ndis_80211_bssid_list_ex *bssid_list; | ||
1687 | struct ndis_80211_bssid_ex *bssid; | ||
1688 | int ret = -EINVAL, len, count, bssid_len; | ||
1689 | |||
1690 | devdbg(usbdev, "SIOCGIWSCAN"); | ||
1691 | |||
1692 | len = CONTROL_BUFFER_SIZE; | ||
1693 | buf = kmalloc(len, GFP_KERNEL); | ||
1694 | if (!buf) { | ||
1695 | ret = -ENOMEM; | ||
1696 | goto out; | ||
1697 | } | ||
1698 | |||
1699 | ret = rndis_query_oid(usbdev, OID_802_11_BSSID_LIST, buf, &len); | ||
1700 | |||
1701 | if (ret != 0) | ||
1702 | goto out; | ||
1703 | |||
1704 | bssid_list = buf; | ||
1705 | bssid = bssid_list->bssid; | ||
1706 | bssid_len = le32_to_cpu(bssid->length); | ||
1707 | count = le32_to_cpu(bssid_list->num_items); | ||
1708 | devdbg(usbdev, "SIOCGIWSCAN: %d BSSIDs found", count); | ||
1709 | |||
1710 | while (count && ((void *)bssid + bssid_len) <= (buf + len)) { | ||
1711 | cev = rndis_translate_scan(dev, info, cev, | ||
1712 | extra + IW_SCAN_MAX_DATA, bssid); | ||
1713 | bssid = (void *)bssid + bssid_len; | ||
1714 | bssid_len = le32_to_cpu(bssid->length); | ||
1715 | count--; | ||
1716 | } | ||
1717 | |||
1718 | out: | ||
1719 | wrqu->data.length = cev - extra; | ||
1720 | wrqu->data.flags = 0; | ||
1721 | kfree(buf); | ||
1722 | return ret; | ||
1723 | } | ||
1724 | |||
1725 | |||
1726 | static int rndis_iw_set_genie(struct net_device *dev, | 1677 | static int rndis_iw_set_genie(struct net_device *dev, |
1727 | struct iw_request_info *info, union iwreq_data *wrqu, char *extra) | 1678 | struct iw_request_info *info, union iwreq_data *wrqu, char *extra) |
1728 | { | 1679 | { |
@@ -2085,8 +2036,8 @@ static const iw_handler rndis_iw_handler[] = | |||
2085 | IW_IOCTL(SIOCGIWRANGE) = (iw_handler) cfg80211_wext_giwrange, | 2036 | IW_IOCTL(SIOCGIWRANGE) = (iw_handler) cfg80211_wext_giwrange, |
2086 | IW_IOCTL(SIOCSIWAP) = rndis_iw_set_bssid, | 2037 | IW_IOCTL(SIOCSIWAP) = rndis_iw_set_bssid, |
2087 | IW_IOCTL(SIOCGIWAP) = rndis_iw_get_bssid, | 2038 | IW_IOCTL(SIOCGIWAP) = rndis_iw_get_bssid, |
2088 | IW_IOCTL(SIOCSIWSCAN) = rndis_iw_set_scan, | 2039 | IW_IOCTL(SIOCSIWSCAN) = (iw_handler) cfg80211_wext_siwscan, |
2089 | IW_IOCTL(SIOCGIWSCAN) = rndis_iw_get_scan, | 2040 | IW_IOCTL(SIOCGIWSCAN) = (iw_handler) cfg80211_wext_giwscan, |
2090 | IW_IOCTL(SIOCSIWESSID) = rndis_iw_set_essid, | 2041 | IW_IOCTL(SIOCSIWESSID) = rndis_iw_set_essid, |
2091 | IW_IOCTL(SIOCGIWESSID) = rndis_iw_get_essid, | 2042 | IW_IOCTL(SIOCGIWESSID) = rndis_iw_get_essid, |
2092 | IW_IOCTL(SIOCSIWNICKN) = rndis_iw_set_nick, | 2043 | IW_IOCTL(SIOCSIWNICKN) = rndis_iw_set_nick, |
@@ -2547,6 +2498,7 @@ static int rndis_wext_bind(struct usbnet *usbdev, struct usb_interface *intf) | |||
2547 | INIT_DELAYED_WORK(&priv->stats_work, rndis_update_wireless_stats); | 2498 | INIT_DELAYED_WORK(&priv->stats_work, rndis_update_wireless_stats); |
2548 | queue_delayed_work(priv->workqueue, &priv->stats_work, | 2499 | queue_delayed_work(priv->workqueue, &priv->stats_work, |
2549 | round_jiffies_relative(STATS_UPDATE_JIFFIES)); | 2500 | round_jiffies_relative(STATS_UPDATE_JIFFIES)); |
2501 | INIT_DELAYED_WORK(&priv->scan_work, rndis_get_scan_results); | ||
2550 | INIT_WORK(&priv->work, rndis_wext_worker); | 2502 | INIT_WORK(&priv->work, rndis_wext_worker); |
2551 | 2503 | ||
2552 | return 0; | 2504 | return 0; |
@@ -2565,6 +2517,7 @@ static void rndis_wext_unbind(struct usbnet *usbdev, struct usb_interface *intf) | |||
2565 | disassociate(usbdev, 0); | 2517 | disassociate(usbdev, 0); |
2566 | 2518 | ||
2567 | cancel_delayed_work_sync(&priv->stats_work); | 2519 | cancel_delayed_work_sync(&priv->stats_work); |
2520 | cancel_delayed_work_sync(&priv->scan_work); | ||
2568 | cancel_work_sync(&priv->work); | 2521 | cancel_work_sync(&priv->work); |
2569 | flush_workqueue(priv->workqueue); | 2522 | flush_workqueue(priv->workqueue); |
2570 | destroy_workqueue(priv->workqueue); | 2523 | destroy_workqueue(priv->workqueue); |