162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Host AP driver Info Frame processing (part of hostap.o module) */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/if_arp.h>
562306a36Sopenharmony_ci#include <linux/sched.h>
662306a36Sopenharmony_ci#include <linux/slab.h>
762306a36Sopenharmony_ci#include <linux/export.h>
862306a36Sopenharmony_ci#include <linux/etherdevice.h>
962306a36Sopenharmony_ci#include "hostap_wlan.h"
1062306a36Sopenharmony_ci#include "hostap.h"
1162306a36Sopenharmony_ci#include "hostap_ap.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
1462306a36Sopenharmony_cistatic void prism2_info_commtallies16(local_info_t *local, unsigned char *buf,
1562306a36Sopenharmony_ci				      int left)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	struct hfa384x_comm_tallies *tallies;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	if (left < sizeof(struct hfa384x_comm_tallies)) {
2062306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: too short (len=%d) commtallies "
2162306a36Sopenharmony_ci		       "info frame\n", local->dev->name, left);
2262306a36Sopenharmony_ci		return;
2362306a36Sopenharmony_ci	}
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	tallies = (struct hfa384x_comm_tallies *) buf;
2662306a36Sopenharmony_ci#define ADD_COMM_TALLIES(name) \
2762306a36Sopenharmony_cilocal->comm_tallies.name += le16_to_cpu(tallies->name)
2862306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_unicast_frames);
2962306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_multicast_frames);
3062306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_fragments);
3162306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_unicast_octets);
3262306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_multicast_octets);
3362306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_deferred_transmissions);
3462306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_single_retry_frames);
3562306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_multiple_retry_frames);
3662306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_retry_limit_exceeded);
3762306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_discards);
3862306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_unicast_frames);
3962306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_multicast_frames);
4062306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_fragments);
4162306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_unicast_octets);
4262306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_multicast_octets);
4362306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_fcs_errors);
4462306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_discards_no_buffer);
4562306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_discards_wrong_sa);
4662306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_discards_wep_undecryptable);
4762306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_message_in_msg_fragments);
4862306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_message_in_bad_msg_fragments);
4962306a36Sopenharmony_ci#undef ADD_COMM_TALLIES
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
5462306a36Sopenharmony_cistatic void prism2_info_commtallies32(local_info_t *local, unsigned char *buf,
5562306a36Sopenharmony_ci				      int left)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct hfa384x_comm_tallies32 *tallies;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (left < sizeof(struct hfa384x_comm_tallies32)) {
6062306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: too short (len=%d) commtallies32 "
6162306a36Sopenharmony_ci		       "info frame\n", local->dev->name, left);
6262306a36Sopenharmony_ci		return;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	tallies = (struct hfa384x_comm_tallies32 *) buf;
6662306a36Sopenharmony_ci#define ADD_COMM_TALLIES(name) \
6762306a36Sopenharmony_cilocal->comm_tallies.name += le32_to_cpu(tallies->name)
6862306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_unicast_frames);
6962306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_multicast_frames);
7062306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_fragments);
7162306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_unicast_octets);
7262306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_multicast_octets);
7362306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_deferred_transmissions);
7462306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_single_retry_frames);
7562306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_multiple_retry_frames);
7662306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_retry_limit_exceeded);
7762306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_discards);
7862306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_unicast_frames);
7962306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_multicast_frames);
8062306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_fragments);
8162306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_unicast_octets);
8262306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_multicast_octets);
8362306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_fcs_errors);
8462306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_discards_no_buffer);
8562306a36Sopenharmony_ci	ADD_COMM_TALLIES(tx_discards_wrong_sa);
8662306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_discards_wep_undecryptable);
8762306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_message_in_msg_fragments);
8862306a36Sopenharmony_ci	ADD_COMM_TALLIES(rx_message_in_bad_msg_fragments);
8962306a36Sopenharmony_ci#undef ADD_COMM_TALLIES
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
9462306a36Sopenharmony_cistatic void prism2_info_commtallies(local_info_t *local, unsigned char *buf,
9562306a36Sopenharmony_ci				    int left)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	if (local->tallies32)
9862306a36Sopenharmony_ci		prism2_info_commtallies32(local, buf, left);
9962306a36Sopenharmony_ci	else
10062306a36Sopenharmony_ci		prism2_info_commtallies16(local, buf, left);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci#ifndef PRISM2_NO_STATION_MODES
10562306a36Sopenharmony_ci#ifndef PRISM2_NO_DEBUG
10662306a36Sopenharmony_cistatic const char* hfa384x_linkstatus_str(u16 linkstatus)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	switch (linkstatus) {
10962306a36Sopenharmony_ci	case HFA384X_LINKSTATUS_CONNECTED:
11062306a36Sopenharmony_ci		return "Connected";
11162306a36Sopenharmony_ci	case HFA384X_LINKSTATUS_DISCONNECTED:
11262306a36Sopenharmony_ci		return "Disconnected";
11362306a36Sopenharmony_ci	case HFA384X_LINKSTATUS_AP_CHANGE:
11462306a36Sopenharmony_ci		return "Access point change";
11562306a36Sopenharmony_ci	case HFA384X_LINKSTATUS_AP_OUT_OF_RANGE:
11662306a36Sopenharmony_ci		return "Access point out of range";
11762306a36Sopenharmony_ci	case HFA384X_LINKSTATUS_AP_IN_RANGE:
11862306a36Sopenharmony_ci		return "Access point in range";
11962306a36Sopenharmony_ci	case HFA384X_LINKSTATUS_ASSOC_FAILED:
12062306a36Sopenharmony_ci		return "Association failed";
12162306a36Sopenharmony_ci	default:
12262306a36Sopenharmony_ci		return "Unknown";
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci#endif /* PRISM2_NO_DEBUG */
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
12962306a36Sopenharmony_cistatic void prism2_info_linkstatus(local_info_t *local, unsigned char *buf,
13062306a36Sopenharmony_ci				    int left)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	u16 val;
13362306a36Sopenharmony_ci	int non_sta_mode;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/* Alloc new JoinRequests to occur since LinkStatus for the previous
13662306a36Sopenharmony_ci	 * has been received */
13762306a36Sopenharmony_ci	local->last_join_time = 0;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	if (left != 2) {
14062306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: invalid linkstatus info frame "
14162306a36Sopenharmony_ci		       "length %d\n", local->dev->name, left);
14262306a36Sopenharmony_ci		return;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	non_sta_mode = local->iw_mode == IW_MODE_MASTER ||
14662306a36Sopenharmony_ci		local->iw_mode == IW_MODE_REPEAT ||
14762306a36Sopenharmony_ci		local->iw_mode == IW_MODE_MONITOR;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	val = buf[0] | (buf[1] << 8);
15062306a36Sopenharmony_ci	if (!non_sta_mode || val != HFA384X_LINKSTATUS_DISCONNECTED) {
15162306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "%s: LinkStatus=%d (%s)\n",
15262306a36Sopenharmony_ci		       local->dev->name, val, hfa384x_linkstatus_str(val));
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (non_sta_mode) {
15662306a36Sopenharmony_ci		netif_carrier_on(local->dev);
15762306a36Sopenharmony_ci		netif_carrier_on(local->ddev);
15862306a36Sopenharmony_ci		return;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* Get current BSSID later in scheduled task */
16262306a36Sopenharmony_ci	set_bit(PRISM2_INFO_PENDING_LINKSTATUS, &local->pending_info);
16362306a36Sopenharmony_ci	local->prev_link_status = val;
16462306a36Sopenharmony_ci	schedule_work(&local->info_queue);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic void prism2_host_roaming(local_info_t *local)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct hfa384x_join_request req;
17162306a36Sopenharmony_ci	struct net_device *dev = local->dev;
17262306a36Sopenharmony_ci	struct hfa384x_hostscan_result *selected, *entry;
17362306a36Sopenharmony_ci	int i;
17462306a36Sopenharmony_ci	unsigned long flags;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	if (local->last_join_time &&
17762306a36Sopenharmony_ci	    time_before(jiffies, local->last_join_time + 10 * HZ)) {
17862306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "%s: last join request has not yet been "
17962306a36Sopenharmony_ci		       "completed - waiting for it before issuing new one\n",
18062306a36Sopenharmony_ci		       dev->name);
18162306a36Sopenharmony_ci		return;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* ScanResults are sorted: first ESS results in decreasing signal
18562306a36Sopenharmony_ci	 * quality then IBSS results in similar order.
18662306a36Sopenharmony_ci	 * Trivial roaming policy: just select the first entry.
18762306a36Sopenharmony_ci	 * This could probably be improved by adding hysteresis to limit
18862306a36Sopenharmony_ci	 * number of handoffs, etc.
18962306a36Sopenharmony_ci	 *
19062306a36Sopenharmony_ci	 * Could do periodic RID_SCANREQUEST or Inquire F101 to get new
19162306a36Sopenharmony_ci	 * ScanResults */
19262306a36Sopenharmony_ci	spin_lock_irqsave(&local->lock, flags);
19362306a36Sopenharmony_ci	if (local->last_scan_results == NULL ||
19462306a36Sopenharmony_ci	    local->last_scan_results_count == 0) {
19562306a36Sopenharmony_ci		spin_unlock_irqrestore(&local->lock, flags);
19662306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "%s: no scan results for host roaming\n",
19762306a36Sopenharmony_ci		       dev->name);
19862306a36Sopenharmony_ci		return;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	selected = &local->last_scan_results[0];
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (local->preferred_ap[0] || local->preferred_ap[1] ||
20462306a36Sopenharmony_ci	    local->preferred_ap[2] || local->preferred_ap[3] ||
20562306a36Sopenharmony_ci	    local->preferred_ap[4] || local->preferred_ap[5]) {
20662306a36Sopenharmony_ci		/* Try to find preferred AP */
20762306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "%s: Preferred AP BSSID %pM\n",
20862306a36Sopenharmony_ci		       dev->name, local->preferred_ap);
20962306a36Sopenharmony_ci		for (i = 0; i < local->last_scan_results_count; i++) {
21062306a36Sopenharmony_ci			entry = &local->last_scan_results[i];
21162306a36Sopenharmony_ci			if (memcmp(local->preferred_ap, entry->bssid, 6) == 0)
21262306a36Sopenharmony_ci			{
21362306a36Sopenharmony_ci				PDEBUG(DEBUG_EXTRA, "%s: using preferred AP "
21462306a36Sopenharmony_ci				       "selection\n", dev->name);
21562306a36Sopenharmony_ci				selected = entry;
21662306a36Sopenharmony_ci				break;
21762306a36Sopenharmony_ci			}
21862306a36Sopenharmony_ci		}
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	memcpy(req.bssid, selected->bssid, ETH_ALEN);
22262306a36Sopenharmony_ci	req.channel = selected->chid;
22362306a36Sopenharmony_ci	spin_unlock_irqrestore(&local->lock, flags);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	PDEBUG(DEBUG_EXTRA, "%s: JoinRequest: BSSID=%pM"
22662306a36Sopenharmony_ci	       " channel=%d\n",
22762306a36Sopenharmony_ci	       dev->name, req.bssid, le16_to_cpu(req.channel));
22862306a36Sopenharmony_ci	if (local->func->set_rid(dev, HFA384X_RID_JOINREQUEST, &req,
22962306a36Sopenharmony_ci				 sizeof(req))) {
23062306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: JoinRequest failed\n", dev->name);
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci	local->last_join_time = jiffies;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic void hostap_report_scan_complete(local_info_t *local)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	union iwreq_data wrqu;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* Inform user space about new scan results (just empty event,
24162306a36Sopenharmony_ci	 * SIOCGIWSCAN can be used to fetch data */
24262306a36Sopenharmony_ci	wrqu.data.length = 0;
24362306a36Sopenharmony_ci	wrqu.data.flags = 0;
24462306a36Sopenharmony_ci	wireless_send_event(local->dev, SIOCGIWSCAN, &wrqu, NULL);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	/* Allow SIOCGIWSCAN handling to occur since we have received
24762306a36Sopenharmony_ci	 * scanning result */
24862306a36Sopenharmony_ci	local->scan_timestamp = 0;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
25362306a36Sopenharmony_cistatic void prism2_info_scanresults(local_info_t *local, unsigned char *buf,
25462306a36Sopenharmony_ci				    int left)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	u16 *pos;
25762306a36Sopenharmony_ci	int new_count, i;
25862306a36Sopenharmony_ci	unsigned long flags;
25962306a36Sopenharmony_ci	struct hfa384x_scan_result *res;
26062306a36Sopenharmony_ci	struct hfa384x_hostscan_result *results, *prev;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	if (left < 4) {
26362306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: invalid scanresult info frame "
26462306a36Sopenharmony_ci		       "length %d\n", local->dev->name, left);
26562306a36Sopenharmony_ci		return;
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	pos = (u16 *) buf;
26962306a36Sopenharmony_ci	pos++;
27062306a36Sopenharmony_ci	pos++;
27162306a36Sopenharmony_ci	left -= 4;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	new_count = left / sizeof(struct hfa384x_scan_result);
27462306a36Sopenharmony_ci	results = kmalloc_array(new_count,
27562306a36Sopenharmony_ci				sizeof(struct hfa384x_hostscan_result),
27662306a36Sopenharmony_ci				GFP_ATOMIC);
27762306a36Sopenharmony_ci	if (results == NULL)
27862306a36Sopenharmony_ci		return;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	/* Convert to hostscan result format. */
28162306a36Sopenharmony_ci	res = (struct hfa384x_scan_result *) pos;
28262306a36Sopenharmony_ci	for (i = 0; i < new_count; i++) {
28362306a36Sopenharmony_ci		memcpy(&results[i], &res[i],
28462306a36Sopenharmony_ci		       sizeof(struct hfa384x_scan_result));
28562306a36Sopenharmony_ci		results[i].atim = 0;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	spin_lock_irqsave(&local->lock, flags);
28962306a36Sopenharmony_ci	local->last_scan_type = PRISM2_SCAN;
29062306a36Sopenharmony_ci	prev = local->last_scan_results;
29162306a36Sopenharmony_ci	local->last_scan_results = results;
29262306a36Sopenharmony_ci	local->last_scan_results_count = new_count;
29362306a36Sopenharmony_ci	spin_unlock_irqrestore(&local->lock, flags);
29462306a36Sopenharmony_ci	kfree(prev);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	hostap_report_scan_complete(local);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/* Perform rest of ScanResults handling later in scheduled task */
29962306a36Sopenharmony_ci	set_bit(PRISM2_INFO_PENDING_SCANRESULTS, &local->pending_info);
30062306a36Sopenharmony_ci	schedule_work(&local->info_queue);
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
30562306a36Sopenharmony_cistatic void prism2_info_hostscanresults(local_info_t *local,
30662306a36Sopenharmony_ci					unsigned char *buf, int left)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	int i, result_size, copy_len, new_count;
30962306a36Sopenharmony_ci	struct hfa384x_hostscan_result *results, *prev;
31062306a36Sopenharmony_ci	unsigned long flags;
31162306a36Sopenharmony_ci	__le16 *pos;
31262306a36Sopenharmony_ci	u8 *ptr;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	wake_up_interruptible(&local->hostscan_wq);
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	if (left < 4) {
31762306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: invalid hostscanresult info frame "
31862306a36Sopenharmony_ci		       "length %d\n", local->dev->name, left);
31962306a36Sopenharmony_ci		return;
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	pos = (__le16 *) buf;
32362306a36Sopenharmony_ci	copy_len = result_size = le16_to_cpu(*pos);
32462306a36Sopenharmony_ci	if (result_size == 0) {
32562306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: invalid result_size (0) in "
32662306a36Sopenharmony_ci		       "hostscanresults\n", local->dev->name);
32762306a36Sopenharmony_ci		return;
32862306a36Sopenharmony_ci	}
32962306a36Sopenharmony_ci	if (copy_len > sizeof(struct hfa384x_hostscan_result))
33062306a36Sopenharmony_ci		copy_len = sizeof(struct hfa384x_hostscan_result);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	pos++;
33362306a36Sopenharmony_ci	pos++;
33462306a36Sopenharmony_ci	left -= 4;
33562306a36Sopenharmony_ci	ptr = (u8 *) pos;
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	new_count = left / result_size;
33862306a36Sopenharmony_ci	results = kcalloc(new_count, sizeof(struct hfa384x_hostscan_result),
33962306a36Sopenharmony_ci			  GFP_ATOMIC);
34062306a36Sopenharmony_ci	if (results == NULL)
34162306a36Sopenharmony_ci		return;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	for (i = 0; i < new_count; i++) {
34462306a36Sopenharmony_ci		memcpy(&results[i], ptr, copy_len);
34562306a36Sopenharmony_ci		ptr += result_size;
34662306a36Sopenharmony_ci		left -= result_size;
34762306a36Sopenharmony_ci	}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	if (left) {
35062306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: short HostScan result entry (%d/%d)\n",
35162306a36Sopenharmony_ci		       local->dev->name, left, result_size);
35262306a36Sopenharmony_ci	}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	spin_lock_irqsave(&local->lock, flags);
35562306a36Sopenharmony_ci	local->last_scan_type = PRISM2_HOSTSCAN;
35662306a36Sopenharmony_ci	prev = local->last_scan_results;
35762306a36Sopenharmony_ci	local->last_scan_results = results;
35862306a36Sopenharmony_ci	local->last_scan_results_count = new_count;
35962306a36Sopenharmony_ci	spin_unlock_irqrestore(&local->lock, flags);
36062306a36Sopenharmony_ci	kfree(prev);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	hostap_report_scan_complete(local);
36362306a36Sopenharmony_ci}
36462306a36Sopenharmony_ci#endif /* PRISM2_NO_STATION_MODES */
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci/* Called only as a tasklet (software IRQ) */
36862306a36Sopenharmony_civoid hostap_info_process(local_info_t *local, struct sk_buff *skb)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	struct hfa384x_info_frame *info;
37162306a36Sopenharmony_ci	unsigned char *buf;
37262306a36Sopenharmony_ci	int left;
37362306a36Sopenharmony_ci#ifndef PRISM2_NO_DEBUG
37462306a36Sopenharmony_ci	int i;
37562306a36Sopenharmony_ci#endif /* PRISM2_NO_DEBUG */
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	info = (struct hfa384x_info_frame *) skb->data;
37862306a36Sopenharmony_ci	buf = skb->data + sizeof(*info);
37962306a36Sopenharmony_ci	left = skb->len - sizeof(*info);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	switch (le16_to_cpu(info->type)) {
38262306a36Sopenharmony_ci	case HFA384X_INFO_COMMTALLIES:
38362306a36Sopenharmony_ci		prism2_info_commtallies(local, buf, left);
38462306a36Sopenharmony_ci		break;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci#ifndef PRISM2_NO_STATION_MODES
38762306a36Sopenharmony_ci	case HFA384X_INFO_LINKSTATUS:
38862306a36Sopenharmony_ci		prism2_info_linkstatus(local, buf, left);
38962306a36Sopenharmony_ci		break;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	case HFA384X_INFO_SCANRESULTS:
39262306a36Sopenharmony_ci		prism2_info_scanresults(local, buf, left);
39362306a36Sopenharmony_ci		break;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	case HFA384X_INFO_HOSTSCANRESULTS:
39662306a36Sopenharmony_ci		prism2_info_hostscanresults(local, buf, left);
39762306a36Sopenharmony_ci		break;
39862306a36Sopenharmony_ci#endif /* PRISM2_NO_STATION_MODES */
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci#ifndef PRISM2_NO_DEBUG
40162306a36Sopenharmony_ci	default:
40262306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "%s: INFO - len=%d type=0x%04x\n",
40362306a36Sopenharmony_ci		       local->dev->name, le16_to_cpu(info->len),
40462306a36Sopenharmony_ci		       le16_to_cpu(info->type));
40562306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "Unknown info frame:");
40662306a36Sopenharmony_ci		for (i = 0; i < (left < 100 ? left : 100); i++)
40762306a36Sopenharmony_ci			PDEBUG2(DEBUG_EXTRA, " %02x", buf[i]);
40862306a36Sopenharmony_ci		PDEBUG2(DEBUG_EXTRA, "\n");
40962306a36Sopenharmony_ci		break;
41062306a36Sopenharmony_ci#endif /* PRISM2_NO_DEBUG */
41162306a36Sopenharmony_ci	}
41262306a36Sopenharmony_ci}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci#ifndef PRISM2_NO_STATION_MODES
41662306a36Sopenharmony_cistatic void handle_info_queue_linkstatus(local_info_t *local)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	int val = local->prev_link_status;
41962306a36Sopenharmony_ci	int connected;
42062306a36Sopenharmony_ci	union iwreq_data wrqu;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	connected =
42362306a36Sopenharmony_ci		val == HFA384X_LINKSTATUS_CONNECTED ||
42462306a36Sopenharmony_ci		val == HFA384X_LINKSTATUS_AP_CHANGE ||
42562306a36Sopenharmony_ci		val == HFA384X_LINKSTATUS_AP_IN_RANGE;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	if (local->func->get_rid(local->dev, HFA384X_RID_CURRENTBSSID,
42862306a36Sopenharmony_ci				 local->bssid, ETH_ALEN, 1) < 0) {
42962306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: could not read CURRENTBSSID after "
43062306a36Sopenharmony_ci		       "LinkStatus event\n", local->dev->name);
43162306a36Sopenharmony_ci	} else {
43262306a36Sopenharmony_ci		PDEBUG(DEBUG_EXTRA, "%s: LinkStatus: BSSID=%pM\n",
43362306a36Sopenharmony_ci		       local->dev->name,
43462306a36Sopenharmony_ci		       (unsigned char *) local->bssid);
43562306a36Sopenharmony_ci		if (local->wds_type & HOSTAP_WDS_AP_CLIENT)
43662306a36Sopenharmony_ci			hostap_add_sta(local->ap, local->bssid);
43762306a36Sopenharmony_ci	}
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	/* Get BSSID if we have a valid AP address */
44062306a36Sopenharmony_ci	if (connected) {
44162306a36Sopenharmony_ci		netif_carrier_on(local->dev);
44262306a36Sopenharmony_ci		netif_carrier_on(local->ddev);
44362306a36Sopenharmony_ci		memcpy(wrqu.ap_addr.sa_data, local->bssid, ETH_ALEN);
44462306a36Sopenharmony_ci	} else {
44562306a36Sopenharmony_ci		netif_carrier_off(local->dev);
44662306a36Sopenharmony_ci		netif_carrier_off(local->ddev);
44762306a36Sopenharmony_ci		eth_zero_addr(wrqu.ap_addr.sa_data);
44862306a36Sopenharmony_ci	}
44962306a36Sopenharmony_ci	wrqu.ap_addr.sa_family = ARPHRD_ETHER;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	/*
45262306a36Sopenharmony_ci	 * Filter out sequential disconnect events in order not to cause a
45362306a36Sopenharmony_ci	 * flood of SIOCGIWAP events that have a race condition with EAPOL
45462306a36Sopenharmony_ci	 * frames and can confuse wpa_supplicant about the current association
45562306a36Sopenharmony_ci	 * status.
45662306a36Sopenharmony_ci	 */
45762306a36Sopenharmony_ci	if (connected || local->prev_linkstatus_connected)
45862306a36Sopenharmony_ci		wireless_send_event(local->dev, SIOCGIWAP, &wrqu, NULL);
45962306a36Sopenharmony_ci	local->prev_linkstatus_connected = connected;
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_cistatic void handle_info_queue_scanresults(local_info_t *local)
46462306a36Sopenharmony_ci{
46562306a36Sopenharmony_ci	if (local->host_roaming == 1 && local->iw_mode == IW_MODE_INFRA)
46662306a36Sopenharmony_ci		prism2_host_roaming(local);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	if (local->host_roaming == 2 && local->iw_mode == IW_MODE_INFRA &&
46962306a36Sopenharmony_ci	    !is_zero_ether_addr(local->preferred_ap)) {
47062306a36Sopenharmony_ci		/*
47162306a36Sopenharmony_ci		 * Firmware seems to be getting into odd state in host_roaming
47262306a36Sopenharmony_ci		 * mode 2 when hostscan is used without join command, so try
47362306a36Sopenharmony_ci		 * to fix this by re-joining the current AP. This does not
47462306a36Sopenharmony_ci		 * actually trigger a new association if the current AP is
47562306a36Sopenharmony_ci		 * still in the scan results.
47662306a36Sopenharmony_ci		 */
47762306a36Sopenharmony_ci		prism2_host_roaming(local);
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci}
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci/* Called only as scheduled task after receiving info frames (used to avoid
48362306a36Sopenharmony_ci * pending too much time in HW IRQ handler). */
48462306a36Sopenharmony_cistatic void handle_info_queue(struct work_struct *work)
48562306a36Sopenharmony_ci{
48662306a36Sopenharmony_ci	local_info_t *local = container_of(work, local_info_t, info_queue);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	if (test_and_clear_bit(PRISM2_INFO_PENDING_LINKSTATUS,
48962306a36Sopenharmony_ci			       &local->pending_info))
49062306a36Sopenharmony_ci		handle_info_queue_linkstatus(local);
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	if (test_and_clear_bit(PRISM2_INFO_PENDING_SCANRESULTS,
49362306a36Sopenharmony_ci			       &local->pending_info))
49462306a36Sopenharmony_ci		handle_info_queue_scanresults(local);
49562306a36Sopenharmony_ci}
49662306a36Sopenharmony_ci#endif /* PRISM2_NO_STATION_MODES */
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_civoid hostap_info_init(local_info_t *local)
50062306a36Sopenharmony_ci{
50162306a36Sopenharmony_ci	skb_queue_head_init(&local->info_list);
50262306a36Sopenharmony_ci#ifndef PRISM2_NO_STATION_MODES
50362306a36Sopenharmony_ci	INIT_WORK(&local->info_queue, handle_info_queue);
50462306a36Sopenharmony_ci#endif /* PRISM2_NO_STATION_MODES */
50562306a36Sopenharmony_ci}
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ciEXPORT_SYMBOL(hostap_info_init);
50962306a36Sopenharmony_ciEXPORT_SYMBOL(hostap_info_process);
510