Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 01632fa4 authored by David Kilroy's avatar David Kilroy Committed by John W. Linville
Browse files

orinoco: Use extended Agere scans available on 9.x series firmwares



This provides more information than the standard Agere scan, including
the WPA IE.

Signed-off-by: default avatarDavid Kilroy <kilroyd@gmail.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 3056c404
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -303,6 +303,40 @@ union hermes_scan_info {
	struct symbol_scan_apinfo	s;
};

/* Extended scan struct for HERMES_INQ_CHANNELINFO.
 * wl_lkm calls this an ACS scan (Automatic Channel Select).
 * Keep out of union hermes_scan_info because it is much bigger than
 * the older scan structures. */
struct agere_ext_scan_info {
	__le16	reserved0;

	u8	noise;
	u8	level;
	u8	rx_flow;
	u8	rate;
	__le16	reserved1[2];

	__le16	frame_control;
	__le16	dur_id;
	u8	addr1[ETH_ALEN];
	u8	addr2[ETH_ALEN];
	u8	bssid[ETH_ALEN];
	__le16	sequence;
	u8	addr4[ETH_ALEN];

	__le16	data_length;

	/* Next 3 fields do not get filled in. */
	u8	daddr[ETH_ALEN];
	u8	saddr[ETH_ALEN];
	__le16	len_type;

	__le64	timestamp;
	__le16	beacon_interval;
	__le16	capabilities;
	u8	data[316];
} __attribute__ ((packed));

#define HERMES_LINKSTATUS_NOT_CONNECTED   (0x0000)  
#define HERMES_LINKSTATUS_CONNECTED       (0x0001)
#define HERMES_LINKSTATUS_DISCONNECTED    (0x0002)
+1 −0
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@
#define HERMES_RID_CNFSCANSSID_AGERE		0xFCB2
#define HERMES_RID_CNFBASICRATES		0xFCB3
#define HERMES_RID_CNFSUPPORTEDRATES		0xFCB4
#define HERMES_RID_CNFSCANCHANNELS2GHZ		0xFCC2
#define HERMES_RID_CNFTICKTIME			0xFCE0
#define HERMES_RID_CNFSCANREQUEST		0xFCE1
#define HERMES_RID_CNFJOINREQUEST		0xFCE2
+405 −43
Original line number Diff line number Diff line
@@ -275,13 +275,19 @@ static inline void set_port_type(struct orinoco_private *priv)
#define ORINOCO_MAX_BSS_COUNT	64
static int orinoco_bss_data_allocate(struct orinoco_private *priv)
{
	if (priv->bss_data)
	if (priv->bss_xbss_data)
		return 0;

	priv->bss_data =
	    kzalloc(ORINOCO_MAX_BSS_COUNT * sizeof(struct bss_element),
	if (priv->has_ext_scan)
		priv->bss_xbss_data = kzalloc(ORINOCO_MAX_BSS_COUNT *
					      sizeof(struct xbss_element),
					      GFP_KERNEL);
	if (!priv->bss_data) {
	else
		priv->bss_xbss_data = kzalloc(ORINOCO_MAX_BSS_COUNT *
					      sizeof(struct bss_element),
					      GFP_KERNEL);

	if (!priv->bss_xbss_data) {
		printk(KERN_WARNING "Out of memory allocating beacons");
		return -ENOMEM;
	}
@@ -290,18 +296,53 @@ static int orinoco_bss_data_allocate(struct orinoco_private *priv)

static void orinoco_bss_data_free(struct orinoco_private *priv)
{
	kfree(priv->bss_data);
	priv->bss_data = NULL;
	kfree(priv->bss_xbss_data);
	priv->bss_xbss_data = NULL;
}

#define PRIV_BSS	((struct bss_element *)priv->bss_xbss_data)
#define PRIV_XBSS	((struct xbss_element *)priv->bss_xbss_data)
static void orinoco_bss_data_init(struct orinoco_private *priv)
{
	int i;

	INIT_LIST_HEAD(&priv->bss_free_list);
	INIT_LIST_HEAD(&priv->bss_list);
	if (priv->has_ext_scan)
		for (i = 0; i < ORINOCO_MAX_BSS_COUNT; i++)
			list_add_tail(&(PRIV_XBSS[i].list),
				      &priv->bss_free_list);
	else
		for (i = 0; i < ORINOCO_MAX_BSS_COUNT; i++)
		list_add_tail(&priv->bss_data[i].list, &priv->bss_free_list);
			list_add_tail(&(PRIV_BSS[i].list),
				      &priv->bss_free_list);

}

static inline u8 *orinoco_get_ie(u8 *data, size_t len,
				 enum ieee80211_mfie eid)
{
	u8 *p = data;
	while ((p + 2) < (data + len)) {
		if (p[0] == eid)
			return p;
		p += p[1] + 2;
	}
	return NULL;
}

#define WPA_OUI_TYPE	"\x00\x50\xF2\x01"
#define WPA_SELECTOR_LEN 4
static inline u8 *orinoco_get_wpa_ie(u8 *data, size_t len)
{
	u8 *p = data;
	while ((p + 2 + WPA_SELECTOR_LEN) < (data + len)) {
		if ((p[0] == MFIE_TYPE_GENERIC) &&
		    (memcmp(&p[2], WPA_OUI_TYPE, WPA_SELECTOR_LEN) == 0))
			return p;
		p += p[1] + 2;
	}
	return NULL;
}


@@ -1414,6 +1455,22 @@ static void orinoco_send_wevents(struct work_struct *work)
static inline void orinoco_clear_scan_results(struct orinoco_private *priv,
					      unsigned long scan_age)
{
	if (priv->has_ext_scan) {
		struct xbss_element *bss;
		struct xbss_element *tmp_bss;

		/* Blow away current list of scan results */
		list_for_each_entry_safe(bss, tmp_bss, &priv->bss_list, list) {
			if (!scan_age ||
			    time_after(jiffies, bss->last_scanned + scan_age)) {
				list_move_tail(&bss->list,
					       &priv->bss_free_list);
				/* Don't blow away ->list, just BSS data */
				memset(&bss->bss, 0, sizeof(bss->bss));
				bss->last_scanned = 0;
			}
		}
	} else {
		struct bss_element *bss;
		struct bss_element *tmp_bss;

@@ -1421,13 +1478,51 @@ static inline void orinoco_clear_scan_results(struct orinoco_private *priv,
		list_for_each_entry_safe(bss, tmp_bss, &priv->bss_list, list) {
			if (!scan_age ||
			    time_after(jiffies, bss->last_scanned + scan_age)) {
			list_move_tail(&bss->list, &priv->bss_free_list);
				list_move_tail(&bss->list,
					       &priv->bss_free_list);
				/* Don't blow away ->list, just BSS data */
			memset(bss, 0, sizeof(bss->bss));
				memset(&bss->bss, 0, sizeof(bss->bss));
				bss->last_scanned = 0;
			}
		}
	}
}

static void orinoco_add_ext_scan_result(struct orinoco_private *priv,
					struct agere_ext_scan_info *atom)
{
	struct xbss_element *bss = NULL;
	int found = 0;

	/* Try to update an existing bss first */
	list_for_each_entry(bss, &priv->bss_list, list) {
		if (compare_ether_addr(bss->bss.bssid, atom->bssid))
			continue;
		/* ESSID lengths */
		if (bss->bss.data[1] != atom->data[1])
			continue;
		if (memcmp(&bss->bss.data[2], &atom->data[2],
			   atom->data[1]))
			continue;
		found = 1;
		break;
	}

	/* Grab a bss off the free list */
	if (!found && !list_empty(&priv->bss_free_list)) {
		bss = list_entry(priv->bss_free_list.next,
				 struct xbss_element, list);
		list_del(priv->bss_free_list.next);

		list_add_tail(&bss->list, &priv->bss_list);
	}

	if (bss) {
		/* Always update the BSS to get latest beacon info */
		memcpy(&bss->bss, atom, sizeof(bss->bss));
		bss->last_scanned = jiffies;
	}
}

static int orinoco_process_scan_results(struct net_device *dev,
					unsigned char *buf,
@@ -1700,6 +1795,63 @@ static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw)
		kfree(buf);
	}
	break;
	case HERMES_INQ_CHANNELINFO:
	{
		struct agere_ext_scan_info *bss;

		if (!priv->scan_inprogress) {
			printk(KERN_DEBUG "%s: Got chaninfo without scan, "
			       "len=%d\n", dev->name, len);
			break;
		}

		/* An empty result indicates that the scan is complete */
		if (len == 0) {
			union iwreq_data	wrqu;

			/* Scan is no longer in progress */
			priv->scan_inprogress = 0;

			wrqu.data.length = 0;
			wrqu.data.flags = 0;
			wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
			break;
		}

		/* Sanity check */
		else if (len > sizeof(*bss)) {
			printk(KERN_WARNING
			       "%s: Ext scan results too large (%d bytes). "
			       "Truncating results to %zd bytes.\n",
			       dev->name, len, sizeof(*bss));
			len = sizeof(*bss);
		} else if (len < (offsetof(struct agere_ext_scan_info,
					   data) + 2)) {
			/* Drop this result now so we don't have to
			 * keep checking later */
			printk(KERN_WARNING
			       "%s: Ext scan results too short (%d bytes)\n",
			       dev->name, len);
			break;
		}

		bss = kmalloc(sizeof(*bss), GFP_ATOMIC);
		if (bss == NULL)
			break;

		/* Read scan data */
		err = hermes_bap_pread(hw, IRQ_BAP, (void *) bss, len,
				       infofid, sizeof(info));
		if (err) {
			kfree(bss);
			break;
		}

		orinoco_add_ext_scan_result(priv, bss);

		kfree(bss);
		break;
	}
	case HERMES_INQ_SEC_STAT_AGERE:
		/* Security status (Agere specific) */
		/* Ignore this frame for now */
@@ -2557,6 +2709,7 @@ static int determine_firmware(struct net_device *dev)
	priv->has_wep = 0;
	priv->has_big_wep = 0;
	priv->has_alt_txcntl = 0;
	priv->has_ext_scan = 0;
	priv->do_fw_download = 0;

	/* Determine capabilities from the firmware version */
@@ -2580,7 +2733,7 @@ static int determine_firmware(struct net_device *dev)
		priv->do_fw_download = 1;
		priv->broken_monitor = (firmver >= 0x80000);
		priv->has_alt_txcntl = (firmver >= 0x90000); /* All 9.x ? */

		priv->has_ext_scan = (firmver >= 0x90000); /* All 9.x ? */
		/* Tested with Agere firmware :
		 *	1.16 ; 4.08 ; 4.52 ; 6.04 ; 6.16 ; 7.28 => Jean II
		 * Tested CableTron firmware : 4.32 => Anton */
@@ -2735,6 +2888,12 @@ static int orinoco_init(struct net_device *dev)
			printk("40-bit key\n");
	}

	/* Now we have the firmware capabilities, allocate appropiate
	 * sized scan buffers */
	if (orinoco_bss_data_allocate(priv))
		goto out;
	orinoco_bss_data_init(priv);

	/* Get the MAC address */
	err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CNFOWNMACADDR,
			      ETH_ALEN, NULL, dev->dev_addr);
@@ -2885,10 +3044,6 @@ struct net_device
		priv->card = NULL;
	priv->dev = device;

	if (orinoco_bss_data_allocate(priv))
		goto err_out_free;
	orinoco_bss_data_init(priv);

	/* Setup / override net_device fields */
	dev->init = orinoco_init;
	dev->hard_start_xmit = orinoco_xmit;
@@ -2924,10 +3079,6 @@ struct net_device
	priv->last_linkstatus = 0xffff;

	return dev;

err_out_free:
	free_netdev(dev);
	return NULL;
}

void free_orinocodev(struct net_device *dev)
@@ -4375,6 +4526,24 @@ static int orinoco_ioctl_setscan(struct net_device *dev,
			if (err)
				break;

			if (priv->has_ext_scan) {
				/* Clear scan results at the start of
				 * an extended scan */
				orinoco_clear_scan_results(priv,
						msecs_to_jiffies(15000));

				/* TODO: Is this available on older firmware?
				 *   Can we use it to scan specific channels
				 *   for IW_SCAN_THIS_FREQ? */
				err = hermes_write_wordrec(hw, USER_BAP,
						HERMES_RID_CNFSCANCHANNELS2GHZ,
						0x7FFF);
				if (err)
					goto out;

				err = hermes_inquire(hw,
						     HERMES_INQ_CHANNELINFO);
			} else
				err = hermes_inquire(hw, HERMES_INQ_SCAN);
			break;
		}
@@ -4541,6 +4710,171 @@ static inline char *orinoco_translate_scan(struct net_device *dev,
	return current_ev;
}

static inline char *orinoco_translate_ext_scan(struct net_device *dev,
					       struct iw_request_info *info,
					       char *current_ev,
					       char *end_buf,
					       struct agere_ext_scan_info *bss,
					       unsigned int last_scanned)
{
	u16			capabilities;
	u16			channel;
	struct iw_event		iwe;		/* Temporary buffer */
	char custom[MAX_CUSTOM_LEN];
	u8 *ie;

	memset(&iwe, 0, sizeof(iwe));

	/* First entry *MUST* be the AP MAC address */
	iwe.cmd = SIOCGIWAP;
	iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
	memcpy(iwe.u.ap_addr.sa_data, bss->bssid, ETH_ALEN);
	current_ev = iwe_stream_add_event(info, current_ev, end_buf,
					  &iwe, IW_EV_ADDR_LEN);

	/* Other entries will be displayed in the order we give them */

	/* Add the ESSID */
	ie = bss->data;
	iwe.u.data.length = ie[1];
	if (iwe.u.data.length) {
		if (iwe.u.data.length > 32)
			iwe.u.data.length = 32;
		iwe.cmd = SIOCGIWESSID;
		iwe.u.data.flags = 1;
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, &ie[2]);
	}

	/* Add mode */
	capabilities = le16_to_cpu(bss->capabilities);
	if (capabilities & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)) {
		iwe.cmd = SIOCGIWMODE;
		if (capabilities & WLAN_CAPABILITY_ESS)
			iwe.u.mode = IW_MODE_MASTER;
		else
			iwe.u.mode = IW_MODE_ADHOC;
		current_ev = iwe_stream_add_event(info, current_ev, end_buf,
						  &iwe, IW_EV_UINT_LEN);
	}

	ie = orinoco_get_ie(bss->data, sizeof(bss->data), MFIE_TYPE_DS_SET);
	channel = ie ? ie[2] : 0;
	if ((channel >= 1) && (channel <= NUM_CHANNELS)) {
		/* Add channel and frequency */
		iwe.cmd = SIOCGIWFREQ;
		iwe.u.freq.m = channel;
		iwe.u.freq.e = 0;
		current_ev = iwe_stream_add_event(info, current_ev, end_buf,
						  &iwe, IW_EV_FREQ_LEN);

		iwe.u.freq.m = channel_frequency[channel-1] * 100000;
		iwe.u.freq.e = 1;
		current_ev = iwe_stream_add_event(info, current_ev, end_buf,
						  &iwe, IW_EV_FREQ_LEN);
	}

	/* Add quality statistics. level and noise in dB. No link quality */
	iwe.cmd = IWEVQUAL;
	iwe.u.qual.updated = IW_QUAL_DBM | IW_QUAL_QUAL_INVALID;
	iwe.u.qual.level = bss->level - 0x95;
	iwe.u.qual.noise = bss->noise - 0x95;
	/* Wireless tools prior to 27.pre22 will show link quality
	 * anyway, so we provide a reasonable value. */
	if (iwe.u.qual.level > iwe.u.qual.noise)
		iwe.u.qual.qual = iwe.u.qual.level - iwe.u.qual.noise;
	else
		iwe.u.qual.qual = 0;
	current_ev = iwe_stream_add_event(info, current_ev, end_buf,
					  &iwe, IW_EV_QUAL_LEN);

	/* Add encryption capability */
	iwe.cmd = SIOCGIWENCODE;
	if (capabilities & WLAN_CAPABILITY_PRIVACY)
		iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
	else
		iwe.u.data.flags = IW_ENCODE_DISABLED;
	iwe.u.data.length = 0;
	current_ev = iwe_stream_add_point(info, current_ev, end_buf,
					  &iwe, NULL);

	/* WPA IE */
	ie = orinoco_get_wpa_ie(bss->data, sizeof(bss->data));
	if (ie) {
		iwe.cmd = IWEVGENIE;
		iwe.u.data.length = ie[1] + 2;
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, ie);
	}

	/* RSN IE */
	ie = orinoco_get_ie(bss->data, sizeof(bss->data), MFIE_TYPE_RSN);
	if (ie) {
		iwe.cmd = IWEVGENIE;
		iwe.u.data.length = ie[1] + 2;
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, ie);
	}

	ie = orinoco_get_ie(bss->data, sizeof(bss->data), MFIE_TYPE_RATES);
	if (ie) {
		char *p = current_ev + iwe_stream_lcp_len(info);
		int i;

		iwe.cmd = SIOCGIWRATE;
		/* Those two flags are ignored... */
		iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;

		for (i = 2; i < (ie[1] + 2); i++) {
			iwe.u.bitrate.value = ((ie[i] & 0x7F) * 500000);
			p = iwe_stream_add_value(info, current_ev, p, end_buf,
						 &iwe, IW_EV_PARAM_LEN);
		}
		/* Check if we added any event */
		if (p > (current_ev + iwe_stream_lcp_len(info)))
			current_ev = p;
	}

	/* Timestamp */
	iwe.cmd = IWEVCUSTOM;
	iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
				     "tsf=%016llx",
				     le64_to_cpu(bss->timestamp));
	if (iwe.u.data.length)
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, custom);

	/* Beacon interval */
	iwe.cmd = IWEVCUSTOM;
	iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
				     "bcn_int=%d",
				     le16_to_cpu(bss->beacon_interval));
	if (iwe.u.data.length)
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, custom);

	/* Capabilites */
	iwe.cmd = IWEVCUSTOM;
	iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
				     "capab=0x%04x",
				     capabilities);
	if (iwe.u.data.length)
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, custom);

	/* Add EXTRA: Age to display seconds since last beacon/probe response
	 * for given network. */
	iwe.cmd = IWEVCUSTOM;
	iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
				     " Last beacon: %dms ago",
				     jiffies_to_msecs(jiffies - last_scanned));
	if (iwe.u.data.length)
		current_ev = iwe_stream_add_point(info, current_ev, end_buf,
						  &iwe, custom);

	return current_ev;
}

/* Return results of a scan */
static int orinoco_ioctl_getscan(struct net_device *dev,
				 struct iw_request_info *info,
@@ -4548,7 +4882,6 @@ static int orinoco_ioctl_getscan(struct net_device *dev,
				 char *extra)
{
	struct orinoco_private *priv = netdev_priv(dev);
	struct bss_element *bss;
	int err = 0;
	unsigned long flags;
	char *current_ev = extra;
@@ -4568,21 +4901,50 @@ static int orinoco_ioctl_getscan(struct net_device *dev,
		goto out;
	}

	if (priv->has_ext_scan) {
		struct xbss_element *bss;

		list_for_each_entry(bss, &priv->bss_list, list) {
		/* Translate to WE format this entry */
		current_ev = orinoco_translate_scan(dev, info, current_ev,
			/* Translate this entry to WE format */
			current_ev =
				orinoco_translate_ext_scan(dev, info,
							   current_ev,
							   extra + srq->length,
							   &bss->bss,
							   bss->last_scanned);

			/* Check if there is space for one more entry */
		if ((extra + srq->length - current_ev) <= IW_EV_ADDR_LEN) {
			/* Ask user space to try again with a bigger buffer */
			if ((extra + srq->length - current_ev)
			    <= IW_EV_ADDR_LEN) {
				/* Ask user space to try again with a
				 * bigger buffer */
				err = -E2BIG;
				goto out;
			}
		}

	} else {
		struct bss_element *bss;

		list_for_each_entry(bss, &priv->bss_list, list) {
			/* Translate this entry to WE format */
			current_ev = orinoco_translate_scan(dev, info,
							    current_ev,
							    extra + srq->length,
							    &bss->bss,
							    bss->last_scanned);

			/* Check if there is space for one more entry */
			if ((extra + srq->length - current_ev)
			    <= IW_EV_ADDR_LEN) {
				/* Ask user space to try again with a
				 * bigger buffer */
				err = -E2BIG;
				goto out;
			}
		}
	}

	srq->length = (current_ev - extra);
	srq->flags = (__u16) priv->scan_mode;

+8 −1
Original line number Diff line number Diff line
@@ -42,6 +42,12 @@ struct bss_element {
	struct list_head list;
};

struct xbss_element {
	struct agere_ext_scan_info bss;
	unsigned long last_scanned;
	struct list_head list;
};

struct orinoco_private {
	void *card;	/* Pointer to card dependent structure */
	struct device *dev;
@@ -86,6 +92,7 @@ struct orinoco_private {
	unsigned int has_sensitivity:1;
	unsigned int has_hostscan:1;
	unsigned int has_alt_txcntl:1;
	unsigned int has_ext_scan:1;
	unsigned int do_fw_download:1;
	unsigned int broken_disableport:1;
	unsigned int broken_monitor:1;
@@ -117,7 +124,7 @@ struct orinoco_private {
	/* Scanning support */
	struct list_head bss_list;
	struct list_head bss_free_list;
	struct bss_element *bss_data;
	void *bss_xbss_data;

	int	scan_inprogress;	/* Scan pending... */
	u32	scan_mode;		/* Type of scan done */