diff options
author | Jussi Kivilinna <jussi.kivilinna@mbnet.fi> | 2010-11-09 12:25:47 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2010-11-16 16:37:04 -0500 |
commit | b5257c952dda24df7078c74b7b811b44c6e49206 (patch) | |
tree | c6d3f9c13344d1ed74aedec0b8a59e4e7716ad23 | |
parent | f23a478075659db8a4fd62fa6e264a8bb052cc5b (diff) |
rndis_wlan: workaround device not returning bss for currently connected AP
BCM4320a devices do not return bss for currently connected AP in bss-list,
althought this is required by NDIS specs. Missing bss leads to warning at
net/wireless/sme.c:__cfg80211_connect_result(), WARN_ON(!bss).
Workaround this by crafting bss manually with information we can read from
device. Workaround is only used when device bss-list does not return current
bss, and so is only used with BCM4320a devices and not newer BCM4320b ones.
Fixes bug #20152.
Reported-by: Luís Picciochi <Pitxyoki@gmail.com>
Signed-off-by: Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r-- | drivers/net/wireless/rndis_wlan.c | 130 |
1 files changed, 121 insertions, 9 deletions
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 71b5971da597..0a423c49aab9 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c | |||
@@ -994,7 +994,8 @@ static int level_to_qual(int level) | |||
994 | */ | 994 | */ |
995 | static int set_infra_mode(struct usbnet *usbdev, int mode); | 995 | static int set_infra_mode(struct usbnet *usbdev, int mode); |
996 | static void restore_keys(struct usbnet *usbdev); | 996 | static void restore_keys(struct usbnet *usbdev); |
997 | static int rndis_check_bssid_list(struct usbnet *usbdev); | 997 | static int rndis_check_bssid_list(struct usbnet *usbdev, u8 *match_bssid, |
998 | bool *matched); | ||
998 | 999 | ||
999 | static int set_essid(struct usbnet *usbdev, struct ndis_80211_ssid *ssid) | 1000 | static int set_essid(struct usbnet *usbdev, struct ndis_80211_ssid *ssid) |
1000 | { | 1001 | { |
@@ -1911,7 +1912,7 @@ static int rndis_scan(struct wiphy *wiphy, struct net_device *dev, | |||
1911 | /* Get current bssid list from device before new scan, as new scan | 1912 | /* Get current bssid list from device before new scan, as new scan |
1912 | * clears internal bssid list. | 1913 | * clears internal bssid list. |
1913 | */ | 1914 | */ |
1914 | rndis_check_bssid_list(usbdev); | 1915 | rndis_check_bssid_list(usbdev, NULL, NULL); |
1915 | 1916 | ||
1916 | if (!request) | 1917 | if (!request) |
1917 | return -EINVAL; | 1918 | return -EINVAL; |
@@ -1981,7 +1982,8 @@ static struct cfg80211_bss *rndis_bss_info_update(struct usbnet *usbdev, | |||
1981 | GFP_KERNEL); | 1982 | GFP_KERNEL); |
1982 | } | 1983 | } |
1983 | 1984 | ||
1984 | static int rndis_check_bssid_list(struct usbnet *usbdev) | 1985 | static int rndis_check_bssid_list(struct usbnet *usbdev, u8 *match_bssid, |
1986 | bool *matched) | ||
1985 | { | 1987 | { |
1986 | void *buf = NULL; | 1988 | void *buf = NULL; |
1987 | struct ndis_80211_bssid_list_ex *bssid_list; | 1989 | struct ndis_80211_bssid_list_ex *bssid_list; |
@@ -2017,7 +2019,11 @@ resize_buf: | |||
2017 | count, len); | 2019 | count, len); |
2018 | 2020 | ||
2019 | while (count && ((void *)bssid + bssid_len) <= (buf + len)) { | 2021 | while (count && ((void *)bssid + bssid_len) <= (buf + len)) { |
2020 | rndis_bss_info_update(usbdev, bssid); | 2022 | if (rndis_bss_info_update(usbdev, bssid) && match_bssid && |
2023 | matched) { | ||
2024 | if (compare_ether_addr(bssid->mac, match_bssid)) | ||
2025 | *matched = true; | ||
2026 | } | ||
2021 | 2027 | ||
2022 | bssid = (void *)bssid + bssid_len; | 2028 | bssid = (void *)bssid + bssid_len; |
2023 | bssid_len = le32_to_cpu(bssid->length); | 2029 | bssid_len = le32_to_cpu(bssid->length); |
@@ -2041,7 +2047,7 @@ static void rndis_get_scan_results(struct work_struct *work) | |||
2041 | if (!priv->scan_request) | 2047 | if (!priv->scan_request) |
2042 | return; | 2048 | return; |
2043 | 2049 | ||
2044 | ret = rndis_check_bssid_list(usbdev); | 2050 | ret = rndis_check_bssid_list(usbdev, NULL, NULL); |
2045 | 2051 | ||
2046 | cfg80211_scan_done(priv->scan_request, ret < 0); | 2052 | cfg80211_scan_done(priv->scan_request, ret < 0); |
2047 | 2053 | ||
@@ -2495,6 +2501,91 @@ static int rndis_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev) | |||
2495 | return rndis_set_oid(usbdev, OID_802_11_PMKID, &pmkid, sizeof(pmkid)); | 2501 | return rndis_set_oid(usbdev, OID_802_11_PMKID, &pmkid, sizeof(pmkid)); |
2496 | } | 2502 | } |
2497 | 2503 | ||
2504 | static void rndis_wlan_craft_connected_bss(struct usbnet *usbdev, u8 *bssid, | ||
2505 | struct ndis_80211_assoc_info *info) | ||
2506 | { | ||
2507 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); | ||
2508 | struct ieee80211_channel *channel; | ||
2509 | struct ndis_80211_conf config; | ||
2510 | struct ndis_80211_ssid ssid; | ||
2511 | s32 signal; | ||
2512 | u64 timestamp; | ||
2513 | u16 capability; | ||
2514 | u16 beacon_interval; | ||
2515 | __le32 rssi; | ||
2516 | u8 ie_buf[34]; | ||
2517 | int len, ret, ie_len; | ||
2518 | |||
2519 | /* Get signal quality, in case of error use rssi=0 and ignore error. */ | ||
2520 | len = sizeof(rssi); | ||
2521 | rssi = 0; | ||
2522 | rndis_query_oid(usbdev, OID_802_11_RSSI, &rssi, &len); | ||
2523 | signal = level_to_qual(le32_to_cpu(rssi)); | ||
2524 | |||
2525 | netdev_dbg(usbdev->net, "%s(): OID_802_11_RSSI -> %d, " | ||
2526 | "rssi:%d, qual: %d\n", __func__, ret, le32_to_cpu(rssi), | ||
2527 | level_to_qual(le32_to_cpu(rssi))); | ||
2528 | |||
2529 | /* Get AP capabilities */ | ||
2530 | if (info) { | ||
2531 | capability = le16_to_cpu(info->resp_ie.capa); | ||
2532 | } else { | ||
2533 | /* Set atleast ESS/IBSS capability */ | ||
2534 | capability = (priv->infra_mode == NDIS_80211_INFRA_INFRA) ? | ||
2535 | WLAN_CAPABILITY_ESS : WLAN_CAPABILITY_IBSS; | ||
2536 | } | ||
2537 | |||
2538 | /* Get channel and beacon interval */ | ||
2539 | len = sizeof(config); | ||
2540 | ret = rndis_query_oid(usbdev, OID_802_11_CONFIGURATION, &config, &len); | ||
2541 | netdev_dbg(usbdev->net, "%s(): OID_802_11_CONFIGURATION -> %d\n", | ||
2542 | __func__, ret); | ||
2543 | if (ret >= 0) { | ||
2544 | beacon_interval = le16_to_cpu(config.beacon_period); | ||
2545 | channel = ieee80211_get_channel(priv->wdev.wiphy, | ||
2546 | KHZ_TO_MHZ(le32_to_cpu(config.ds_config))); | ||
2547 | if (!channel) { | ||
2548 | netdev_warn(usbdev->net, "%s(): could not get channel." | ||
2549 | "\n", __func__); | ||
2550 | return; | ||
2551 | } | ||
2552 | } else { | ||
2553 | netdev_warn(usbdev->net, "%s(): could not get configuration.\n", | ||
2554 | __func__); | ||
2555 | return; | ||
2556 | } | ||
2557 | |||
2558 | /* Get SSID, in case of error, use zero length SSID and ignore error. */ | ||
2559 | len = sizeof(ssid); | ||
2560 | memset(&ssid, 0, sizeof(ssid)); | ||
2561 | ret = rndis_query_oid(usbdev, OID_802_11_SSID, &ssid, &len); | ||
2562 | netdev_dbg(usbdev->net, "%s(): OID_802_11_SSID -> %d, len: %d, ssid: " | ||
2563 | "'%.32s'\n", __func__, ret, | ||
2564 | le32_to_cpu(ssid.length), ssid.essid); | ||
2565 | |||
2566 | if (le32_to_cpu(ssid.length) > 32) | ||
2567 | ssid.length = cpu_to_le32(32); | ||
2568 | |||
2569 | ie_buf[0] = WLAN_EID_SSID; | ||
2570 | ie_buf[1] = le32_to_cpu(ssid.length); | ||
2571 | memcpy(&ie_buf[2], ssid.essid, le32_to_cpu(ssid.length)); | ||
2572 | |||
2573 | ie_len = le32_to_cpu(ssid.length) + 2; | ||
2574 | |||
2575 | /* no tsf */ | ||
2576 | timestamp = 0; | ||
2577 | |||
2578 | netdev_dbg(usbdev->net, "%s(): channel:%d(freq), bssid:[%pM], tsf:%d, " | ||
2579 | "capa:%x, beacon int:%d, resp_ie(len:%d, essid:'%.32s'), " | ||
2580 | "signal:%d\n", __func__, (channel ? channel->center_freq : -1), | ||
2581 | bssid, (u32)timestamp, capability, beacon_interval, ie_len, | ||
2582 | ssid.essid, signal); | ||
2583 | |||
2584 | cfg80211_inform_bss(priv->wdev.wiphy, channel, bssid, | ||
2585 | timestamp, capability, beacon_interval, ie_buf, ie_len, | ||
2586 | signal, GFP_KERNEL); | ||
2587 | } | ||
2588 | |||
2498 | /* | 2589 | /* |
2499 | * workers, indication handlers, device poller | 2590 | * workers, indication handlers, device poller |
2500 | */ | 2591 | */ |
@@ -2507,6 +2598,7 @@ static void rndis_wlan_do_link_up_work(struct usbnet *usbdev) | |||
2507 | u8 *req_ie, *resp_ie; | 2598 | u8 *req_ie, *resp_ie; |
2508 | int ret, offset; | 2599 | int ret, offset; |
2509 | bool roamed = false; | 2600 | bool roamed = false; |
2601 | bool match_bss; | ||
2510 | 2602 | ||
2511 | if (priv->infra_mode == NDIS_80211_INFRA_INFRA && priv->connected) { | 2603 | if (priv->infra_mode == NDIS_80211_INFRA_INFRA && priv->connected) { |
2512 | /* received media connect indication while connected, either | 2604 | /* received media connect indication while connected, either |
@@ -2558,6 +2650,13 @@ static void rndis_wlan_do_link_up_work(struct usbnet *usbdev) | |||
2558 | resp_ie_len = | 2650 | resp_ie_len = |
2559 | CONTROL_BUFFER_SIZE - offset; | 2651 | CONTROL_BUFFER_SIZE - offset; |
2560 | } | 2652 | } |
2653 | } else { | ||
2654 | /* Since rndis_wlan_craft_connected_bss() might use info | ||
2655 | * later and expects info to contain valid data if | ||
2656 | * non-null, free info and set NULL here. | ||
2657 | */ | ||
2658 | kfree(info); | ||
2659 | info = NULL; | ||
2561 | } | 2660 | } |
2562 | } else if (WARN_ON(priv->infra_mode != NDIS_80211_INFRA_ADHOC)) | 2661 | } else if (WARN_ON(priv->infra_mode != NDIS_80211_INFRA_ADHOC)) |
2563 | return; | 2662 | return; |
@@ -2569,13 +2668,26 @@ static void rndis_wlan_do_link_up_work(struct usbnet *usbdev) | |||
2569 | netdev_dbg(usbdev->net, "link up work: [%pM]%s\n", | 2668 | netdev_dbg(usbdev->net, "link up work: [%pM]%s\n", |
2570 | bssid, roamed ? " roamed" : ""); | 2669 | bssid, roamed ? " roamed" : ""); |
2571 | 2670 | ||
2572 | /* Internal bss list in device always contains at least the currently | 2671 | /* Internal bss list in device should contain at least the currently |
2573 | * connected bss and we can get it to cfg80211 with | 2672 | * connected bss and we can get it to cfg80211 with |
2574 | * rndis_check_bssid_list(). | 2673 | * rndis_check_bssid_list(). |
2575 | * NOTE: This is true for Broadcom chip, but not mentioned in RNDIS | 2674 | * |
2576 | * spec. | 2675 | * NDIS spec says: "If the device is associated, but the associated |
2676 | * BSSID is not in its BSSID scan list, then the driver must add an | ||
2677 | * entry for the BSSID at the end of the data that it returns in | ||
2678 | * response to query of OID_802_11_BSSID_LIST." | ||
2679 | * | ||
2680 | * NOTE: Seems to be true for BCM4320b variant, but not BCM4320a. | ||
2577 | */ | 2681 | */ |
2578 | rndis_check_bssid_list(usbdev); | 2682 | match_bss = false; |
2683 | rndis_check_bssid_list(usbdev, bssid, &match_bss); | ||
2684 | |||
2685 | if (!is_zero_ether_addr(bssid) && !match_bss) { | ||
2686 | /* Couldn't get bss from device, we need to manually craft bss | ||
2687 | * for cfg80211. | ||
2688 | */ | ||
2689 | rndis_wlan_craft_connected_bss(usbdev, bssid, info); | ||
2690 | } | ||
2579 | 2691 | ||
2580 | if (priv->infra_mode == NDIS_80211_INFRA_INFRA) { | 2692 | if (priv->infra_mode == NDIS_80211_INFRA_INFRA) { |
2581 | if (!roamed) | 2693 | if (!roamed) |