Esempio n. 1
0
void orinoco_add_extscan_result(struct orinoco_private *priv,
				struct agere_ext_scan_info *bss,
				size_t len)
{
	struct wiphy *wiphy = priv_to_wiphy(priv);
	struct ieee80211_channel *channel;
	struct cfg80211_bss *cbss;
	const u8 *ie;
	u64 timestamp;
	s32 signal;
	u16 capability;
	u16 beacon_interval;
	size_t ie_len;
	int chan, freq;

	ie_len = len - sizeof(*bss);
	ie = cfg80211_find_ie(WLAN_EID_DS_PARAMS, bss->data, ie_len);
	chan = ie ? ie[2] : 0;
	freq = ieee80211_dsss_chan_to_freq(chan);
	channel = ieee80211_get_channel(wiphy, freq);

	timestamp = le64_to_cpu(bss->timestamp);
	capability = le16_to_cpu(bss->capabilities);
	beacon_interval = le16_to_cpu(bss->beacon_interval);
	ie = bss->data;
	signal = SIGNAL_TO_MBM(bss->level);

	cbss = cfg80211_inform_bss(wiphy, channel, bss->bssid, timestamp,
				   capability, beacon_interval, ie, ie_len,
				   signal, GFP_KERNEL);
	cfg80211_put_bss(cbss);
}
Esempio n. 2
0
static void orinoco_add_hostscan_result(struct orinoco_private *priv,
					const union hermes_scan_info *bss)
{
	struct wiphy *wiphy = priv_to_wiphy(priv);
	struct ieee80211_channel *channel;
	struct cfg80211_bss *cbss;
	u8 *ie;
	u8 ie_buf[46];
	u64 timestamp;
	s32 signal;
	u16 capability;
	u16 beacon_interval;
	int ie_len;
	int freq;
	int len;

	len = le16_to_cpu(bss->a.essid_len);

	/* Reconstruct SSID and bitrate IEs to pass up */
	ie_buf[0] = WLAN_EID_SSID;
	ie_buf[1] = len;
	memcpy(&ie_buf[2], bss->a.essid, len);

	ie = ie_buf + len + 2;
	ie_len = ie_buf[1] + 2;
	switch (priv->firmware_type) {
	case FIRMWARE_TYPE_SYMBOL:
		ie_len += symbol_build_supp_rates(ie, bss->s.rates);
		break;

	case FIRMWARE_TYPE_INTERSIL:
		ie_len += prism_build_supp_rates(ie, bss->p.rates);
		break;

	case FIRMWARE_TYPE_AGERE:
	default:
		break;
	}

	freq = ieee80211_dsss_chan_to_freq(le16_to_cpu(bss->a.channel));
	channel = ieee80211_get_channel(wiphy, freq);
	if (!channel) {
		printk(KERN_DEBUG "Invalid channel designation %04X(%04X)",
			bss->a.channel, freq);
		return;	/* Then ignore it for now */
	}
	timestamp = 0;
	capability = le16_to_cpu(bss->a.capabilities);
	beacon_interval = le16_to_cpu(bss->a.beacon_interv);
	signal = SIGNAL_TO_MBM(le16_to_cpu(bss->a.level));

	cbss = cfg80211_inform_bss(wiphy, channel, bss->a.bssid, timestamp,
				   capability, beacon_interval, ie_buf, ie_len,
				   signal, GFP_KERNEL);
	cfg80211_put_bss(cbss);
}
Esempio n. 3
0
File: core.c Progetto: mdamt/linux
static void qtnf_mac_init_primary_intf(struct qtnf_wmac *mac)
{
	struct qtnf_vif *vif = &mac->iflist[QTNF_PRIMARY_VIF_IDX];

	vif->wdev.iftype = NL80211_IFTYPE_AP;
	vif->bss_priority = QTNF_DEF_BSS_PRIORITY;
	vif->wdev.wiphy = priv_to_wiphy(mac);
	INIT_WORK(&vif->reset_work, qtnf_vif_reset_handler);
	vif->cons_tx_timeout_cnt = 0;
}
Esempio n. 4
0
static void orinoco_pci_remove_one(struct pci_dev *pdev)
{
	struct orinoco_private *priv = pci_get_drvdata(pdev);

	orinoco_if_del(priv);
	wiphy_unregister(priv_to_wiphy(priv));
	free_irq(pdev->irq, priv);
	free_orinocodev(priv);
	pci_iounmap(pdev, priv->hw.iobase);
	pci_release_regions(pdev);
	pci_disable_device(pdev);
}
Esempio n. 5
0
static int
qtnf_event_handle_freq_change(struct qtnf_wmac *mac,
			      const struct qlink_event_freq_change *data,
			      u16 len)
{
	struct wiphy *wiphy = priv_to_wiphy(mac);
	struct cfg80211_chan_def chandef;
	struct qtnf_vif *vif;
	int i;

	if (len < sizeof(*data)) {
		pr_err("MAC%u: payload is too short\n", mac->macid);
		return -EINVAL;
	}

	if (!wiphy->registered)
		return 0;

	qlink_chandef_q2cfg(wiphy, &data->chan, &chandef);

	if (!cfg80211_chandef_valid(&chandef)) {
		pr_err("MAC%u: bad channel freq=%u cf1=%u cf2=%u bw=%u\n",
		       mac->macid, chandef.chan->center_freq,
		       chandef.center_freq1, chandef.center_freq2,
		       chandef.width);
		return -EINVAL;
	}

	pr_debug("MAC%d: new channel ieee=%u freq1=%u freq2=%u bw=%u\n",
		 mac->macid, chandef.chan->hw_value, chandef.center_freq1,
		 chandef.center_freq2, chandef.width);

	for (i = 0; i < QTNF_MAX_INTF; i++) {
		vif = &mac->iflist[i];
		if (vif->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED)
			continue;

		if (vif->netdev) {
			mutex_lock(&vif->wdev.mtx);
			cfg80211_ch_switch_notify(vif->netdev, &chandef);
			mutex_unlock(&vif->wdev.mtx);
		}
	}

	return 0;
}
Esempio n. 6
0
File: core.c Progetto: mdamt/linux
static void qtnf_core_mac_detach(struct qtnf_bus *bus, unsigned int macid)
{
	struct qtnf_wmac *mac;
	struct wiphy *wiphy;
	struct qtnf_vif *vif;
	unsigned int i;
	enum nl80211_band band;

	mac = bus->mac[macid];

	if (!mac)
		return;

	wiphy = priv_to_wiphy(mac);

	for (i = 0; i < QTNF_MAX_INTF; i++) {
		vif = &mac->iflist[i];
		rtnl_lock();
		if (vif->netdev &&
		    vif->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) {
			qtnf_virtual_intf_cleanup(vif->netdev);
			qtnf_del_virtual_intf(wiphy, &vif->wdev);
		}
		rtnl_unlock();
		qtnf_sta_list_free(&vif->sta_list);
	}

	if (mac->wiphy_registered)
		wiphy_unregister(wiphy);

	for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; ++band) {
		if (!wiphy->bands[band])
			continue;

		kfree(wiphy->bands[band]->channels);
		wiphy->bands[band]->n_channels = 0;

		kfree(wiphy->bands[band]);
		wiphy->bands[band] = NULL;
	}

	kfree(mac->macinfo.limits);
	kfree(wiphy->iface_combinations);
	wiphy_free(wiphy);
	bus->mac[macid] = NULL;
}
Esempio n. 7
0
File: core.c Progetto: mdamt/linux
int qtnf_core_net_attach(struct qtnf_wmac *mac, struct qtnf_vif *vif,
			 const char *name, unsigned char name_assign_type,
			 enum nl80211_iftype iftype)
{
	struct wiphy *wiphy = priv_to_wiphy(mac);
	struct net_device *dev;
	void *qdev_vif;
	int ret;

	dev = alloc_netdev_mqs(sizeof(struct qtnf_vif *), name,
			       name_assign_type, ether_setup, 1, 1);
	if (!dev) {
		memset(&vif->wdev, 0, sizeof(vif->wdev));
		vif->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED;
		return -ENOMEM;
	}

	vif->netdev = dev;

	dev->netdev_ops = &qtnf_netdev_ops;
	dev->needs_free_netdev = true;
	dev_net_set(dev, wiphy_net(wiphy));
	dev->ieee80211_ptr = &vif->wdev;
	dev->ieee80211_ptr->iftype = iftype;
	ether_addr_copy(dev->dev_addr, vif->mac_addr);
	SET_NETDEV_DEV(dev, wiphy_dev(wiphy));
	dev->flags |= IFF_BROADCAST | IFF_MULTICAST;
	dev->watchdog_timeo = QTNF_DEF_WDOG_TIMEOUT;
	dev->tx_queue_len = 100;

	qdev_vif = netdev_priv(dev);
	*((void **)qdev_vif) = vif;

	SET_NETDEV_DEV(dev, mac->bus->dev);

	ret = register_netdevice(dev);
	if (ret) {
		free_netdev(dev);
		vif->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED;
	}

	return ret;
}
Esempio n. 8
0
static void qtnf_cfg80211_reg_notifier(struct wiphy *wiphy_in,
				       struct regulatory_request *req)
{
	struct qtnf_wmac *mac = wiphy_priv(wiphy_in);
	struct qtnf_bus *bus = mac->bus;
	struct wiphy *wiphy;
	unsigned int mac_idx;
	enum nl80211_band band;
	int ret;

	pr_debug("MAC%u: initiator=%d alpha=%c%c\n", mac->macid, req->initiator,
		 req->alpha2[0], req->alpha2[1]);

	ret = qtnf_cmd_reg_notify(bus, req);
	if (ret) {
		if (ret != -EOPNOTSUPP && ret != -EALREADY)
			pr_err("failed to update reg domain to %c%c\n",
			       req->alpha2[0], req->alpha2[1]);
		return;
	}

	for (mac_idx = 0; mac_idx < QTNF_MAX_MAC; ++mac_idx) {
		if (!(bus->hw_info.mac_bitmap & (1 << mac_idx)))
			continue;

		mac = bus->mac[mac_idx];
		wiphy = priv_to_wiphy(mac);

		for (band = 0; band < NUM_NL80211_BANDS; ++band) {
			if (!wiphy->bands[band])
				continue;

			ret = qtnf_cmd_get_mac_chan_info(mac,
							 wiphy->bands[band]);
			if (ret)
				pr_err("failed to get chan info for mac %u band %u\n",
				       mac_idx, band);
		}
	}
}
Esempio n. 9
0
File: core.c Progetto: mdamt/linux
static int qtnf_mac_init_bands(struct qtnf_wmac *mac)
{
	struct wiphy *wiphy = priv_to_wiphy(mac);
	int ret = 0;

	if (mac->macinfo.bands_cap & QLINK_BAND_2GHZ) {
		ret = qtnf_mac_init_single_band(wiphy, mac, NL80211_BAND_2GHZ);
		if (ret)
			goto out;
	}

	if (mac->macinfo.bands_cap & QLINK_BAND_5GHZ) {
		ret = qtnf_mac_init_single_band(wiphy, mac, NL80211_BAND_5GHZ);
		if (ret)
			goto out;
	}

	if (mac->macinfo.bands_cap & QLINK_BAND_60GHZ)
		ret = qtnf_mac_init_single_band(wiphy, mac, NL80211_BAND_60GHZ);

out:
	return ret;
}
Esempio n. 10
0
static int
qtnf_event_handle_external_auth(struct qtnf_vif *vif,
				const struct qlink_event_external_auth *ev,
				u16 len)
{
	struct cfg80211_external_auth_params auth = {0};
	struct wiphy *wiphy = priv_to_wiphy(vif->mac);
	int ret;

	if (len < sizeof(*ev)) {
		pr_err("MAC%u: payload is too short\n", vif->mac->macid);
		return -EINVAL;
	}

	if (!wiphy->registered || !vif->netdev)
		return 0;

	if (ev->ssid_len) {
		memcpy(auth.ssid.ssid, ev->ssid, ev->ssid_len);
		auth.ssid.ssid_len = ev->ssid_len;
	}

	auth.key_mgmt_suite = le32_to_cpu(ev->akm_suite);
	ether_addr_copy(auth.bssid, ev->bssid);
	auth.action = ev->action;

	pr_info("%s: external auth bss=%pM action=%u akm=%u\n",
		vif->netdev->name, auth.bssid, auth.action,
		auth.key_mgmt_suite);

	ret = cfg80211_external_auth_request(vif->netdev, &auth, GFP_KERNEL);
	if (ret)
		pr_warn("failed to offload external auth request\n");

	return ret;
}
Esempio n. 11
0
static int orinoco_pci_init_one(struct pci_dev *pdev,
				const struct pci_device_id *ent)
{
	int err;
	struct orinoco_private *priv;
	struct orinoco_pci_card *card;
	void __iomem *hermes_io;

	err = pci_enable_device(pdev);
	if (err) {
		printk(KERN_ERR PFX "Cannot enable PCI device\n");
		return err;
	}

	err = pci_request_regions(pdev, DRIVER_NAME);
	if (err) {
		printk(KERN_ERR PFX "Cannot obtain PCI resources\n");
		goto fail_resources;
	}

	hermes_io = pci_iomap(pdev, 0, 0);
	if (!hermes_io) {
		printk(KERN_ERR PFX "Cannot remap chipset registers\n");
		err = -EIO;
		goto fail_map_hermes;
	}

	/* Allocate network device */
	priv = alloc_orinocodev(sizeof(*card), &pdev->dev,
				orinoco_pci_cor_reset, NULL);
	if (!priv) {
		printk(KERN_ERR PFX "Cannot allocate network device\n");
		err = -ENOMEM;
		goto fail_alloc;
	}

	card = priv->card;

	hermes_struct_init(&priv->hw, hermes_io, HERMES_32BIT_REGSPACING);

	err = request_irq(pdev->irq, orinoco_interrupt, IRQF_SHARED,
			  DRIVER_NAME, priv);
	if (err) {
		printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq);
		err = -EBUSY;
		goto fail_irq;
	}

	err = orinoco_pci_cor_reset(priv);
	if (err) {
		printk(KERN_ERR PFX "Initial reset failed\n");
		goto fail;
	}

	err = orinoco_init(priv);
	if (err) {
		printk(KERN_ERR PFX "orinoco_init() failed\n");
		goto fail;
	}

	err = orinoco_if_add(priv, 0, 0, NULL);
	if (err) {
		printk(KERN_ERR PFX "orinoco_if_add() failed\n");
		goto fail_wiphy;
	}

	pci_set_drvdata(pdev, priv);

	return 0;

 fail_wiphy:
	wiphy_unregister(priv_to_wiphy(priv));
 fail:
	free_irq(pdev->irq, priv);

 fail_irq:
	free_orinocodev(priv);

 fail_alloc:
	pci_iounmap(pdev, hermes_io);

 fail_map_hermes:
	pci_release_regions(pdev);

 fail_resources:
	pci_disable_device(pdev);

	return err;
}
Esempio n. 12
0
int qtnf_wiphy_register(struct qtnf_hw_info *hw_info, struct qtnf_wmac *mac)
{
	struct wiphy *wiphy = priv_to_wiphy(mac);
	struct ieee80211_iface_combination *iface_comb = NULL;
	int ret;

	if (!wiphy) {
		pr_err("invalid wiphy pointer\n");
		return -EFAULT;
	}

	iface_comb = kzalloc(sizeof(*iface_comb), GFP_KERNEL);
	if (!iface_comb)
		return -ENOMEM;

	ret = qtnf_wiphy_setup_if_comb(wiphy, iface_comb, &mac->macinfo);
	if (ret)
		goto out;

	pr_info("MAC%u: phymode=%#x radar=%#x\n", mac->macid,
		mac->macinfo.phymode_cap, mac->macinfo.radar_detect_widths);

	wiphy->frag_threshold = mac->macinfo.frag_thr;
	wiphy->rts_threshold = mac->macinfo.rts_thr;
	wiphy->retry_short = mac->macinfo.sretry_limit;
	wiphy->retry_long = mac->macinfo.lretry_limit;
	wiphy->coverage_class = mac->macinfo.coverage_class;

	wiphy->max_scan_ssids = QTNF_MAX_SSID_LIST_LENGTH;
	wiphy->max_scan_ie_len = QTNF_MAX_VSIE_LEN;
	wiphy->mgmt_stypes = qtnf_mgmt_stypes;
	wiphy->max_remain_on_channel_duration = 5000;

	wiphy->iface_combinations = iface_comb;
	wiphy->n_iface_combinations = 1;
	wiphy->max_num_csa_counters = 2;

	/* Initialize cipher suits */
	wiphy->cipher_suites = qtnf_cipher_suites;
	wiphy->n_cipher_suites = ARRAY_SIZE(qtnf_cipher_suites);
	wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
	wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME |
			WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD |
			WIPHY_FLAG_AP_UAPSD |
			WIPHY_FLAG_HAS_CHANNEL_SWITCH;

	wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
				    NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2;

	wiphy->available_antennas_tx = mac->macinfo.num_tx_chain;
	wiphy->available_antennas_rx = mac->macinfo.num_rx_chain;

	wiphy->max_ap_assoc_sta = mac->macinfo.max_ap_assoc_sta;

	ether_addr_copy(wiphy->perm_addr, mac->macaddr);

	if (hw_info->hw_capab & QLINK_HW_SUPPORTS_REG_UPDATE) {
		wiphy->regulatory_flags |= REGULATORY_STRICT_REG |
			REGULATORY_CUSTOM_REG;
		wiphy->reg_notifier = qtnf_cfg80211_reg_notifier;
		wiphy_apply_custom_regulatory(wiphy, hw_info->rd);
	} else {
		wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
	}

	ret = wiphy_register(wiphy);
	if (ret < 0)
		goto out;

	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
		ret = regulatory_set_wiphy_regd(wiphy, hw_info->rd);
	else if (isalpha(hw_info->rd->alpha2[0]) &&
		 isalpha(hw_info->rd->alpha2[1]))
		ret = regulatory_hint(wiphy, hw_info->rd->alpha2);

out:
	if (ret) {
		kfree(iface_comb);
		return ret;
	}

	return 0;
}
Esempio n. 13
0
static int qtnf_event_handle_radar(struct qtnf_vif *vif,
				   const struct qlink_event_radar *ev,
				   u16 len)
{
	struct wiphy *wiphy = priv_to_wiphy(vif->mac);
	struct cfg80211_chan_def chandef;

	if (len < sizeof(*ev)) {
		pr_err("MAC%u: payload is too short\n", vif->mac->macid);
		return -EINVAL;
	}

	if (!wiphy->registered || !vif->netdev)
		return 0;

	qlink_chandef_q2cfg(wiphy, &ev->chan, &chandef);

	if (!cfg80211_chandef_valid(&chandef)) {
		pr_err("MAC%u: bad channel f1=%u f2=%u bw=%u\n",
		       vif->mac->macid,
		       chandef.center_freq1, chandef.center_freq2,
		       chandef.width);
		return -EINVAL;
	}

	pr_info("%s: radar event=%u f1=%u f2=%u bw=%u\n",
		vif->netdev->name, ev->event,
		chandef.center_freq1, chandef.center_freq2,
		chandef.width);

	switch (ev->event) {
	case QLINK_RADAR_DETECTED:
		cfg80211_radar_event(wiphy, &chandef, GFP_KERNEL);
		break;
	case QLINK_RADAR_CAC_FINISHED:
		if (!vif->wdev.cac_started)
			break;

		cfg80211_cac_event(vif->netdev, &chandef,
				   NL80211_RADAR_CAC_FINISHED, GFP_KERNEL);
		break;
	case QLINK_RADAR_CAC_ABORTED:
		if (!vif->wdev.cac_started)
			break;

		cfg80211_cac_event(vif->netdev, &chandef,
				   NL80211_RADAR_CAC_ABORTED, GFP_KERNEL);
		break;
	case QLINK_RADAR_CAC_STARTED:
		if (vif->wdev.cac_started)
			break;

		if (!wiphy_ext_feature_isset(wiphy,
					     NL80211_EXT_FEATURE_DFS_OFFLOAD))
			break;

		cfg80211_cac_event(vif->netdev, &chandef,
				   NL80211_RADAR_CAC_STARTED, GFP_KERNEL);
		break;
	default:
		pr_warn("%s: unhandled radar event %u\n",
			vif->netdev->name, ev->event);
		break;
	}

	return 0;
}
Esempio n. 14
0
static int
qtnf_event_handle_scan_results(struct qtnf_vif *vif,
			       const struct qlink_event_scan_result *sr,
			       u16 len)
{
	struct cfg80211_bss *bss;
	struct ieee80211_channel *channel;
	struct wiphy *wiphy = priv_to_wiphy(vif->mac);
	enum cfg80211_bss_frame_type frame_type = CFG80211_BSS_FTYPE_UNKNOWN;
	size_t payload_len;
	u16 tlv_type;
	u16 tlv_value_len;
	size_t tlv_full_len;
	const struct qlink_tlv_hdr *tlv;
	const u8 *ies = NULL;
	size_t ies_len = 0;

	if (len < sizeof(*sr)) {
		pr_err("VIF%u.%u: payload is too short\n", vif->mac->macid,
		       vif->vifid);
		return -EINVAL;
	}

	channel = ieee80211_get_channel(wiphy, le16_to_cpu(sr->freq));
	if (!channel) {
		pr_err("VIF%u.%u: channel at %u MHz not found\n",
		       vif->mac->macid, vif->vifid, le16_to_cpu(sr->freq));
		return -EINVAL;
	}

	payload_len = len - sizeof(*sr);
	tlv = (struct qlink_tlv_hdr *)sr->payload;

	while (payload_len >= sizeof(struct qlink_tlv_hdr)) {
		tlv_type = le16_to_cpu(tlv->type);
		tlv_value_len = le16_to_cpu(tlv->len);
		tlv_full_len = tlv_value_len + sizeof(struct qlink_tlv_hdr);

		if (tlv_full_len > payload_len)
			return -EINVAL;

		if (tlv_type == QTN_TLV_ID_IE_SET) {
			const struct qlink_tlv_ie_set *ie_set;
			unsigned int ie_len;

			if (payload_len < sizeof(*ie_set))
				return -EINVAL;

			ie_set = (const struct qlink_tlv_ie_set *)tlv;
			ie_len = tlv_value_len -
				(sizeof(*ie_set) - sizeof(ie_set->hdr));

			switch (ie_set->type) {
			case QLINK_IE_SET_BEACON_IES:
				frame_type = CFG80211_BSS_FTYPE_BEACON;
				break;
			case QLINK_IE_SET_PROBE_RESP_IES:
				frame_type = CFG80211_BSS_FTYPE_PRESP;
				break;
			default:
				frame_type = CFG80211_BSS_FTYPE_UNKNOWN;
			}

			if (ie_len) {
				ies = ie_set->ie_data;
				ies_len = ie_len;
			}
		}

		payload_len -= tlv_full_len;
		tlv = (struct qlink_tlv_hdr *)(tlv->val + tlv_value_len);
	}

	if (payload_len)
		return -EINVAL;

	bss = cfg80211_inform_bss(wiphy, channel, frame_type,
				  sr->bssid, get_unaligned_le64(&sr->tsf),
				  le16_to_cpu(sr->capab),
				  le16_to_cpu(sr->bintval), ies, ies_len,
				  DBM_TO_MBM(sr->sig_dbm), GFP_KERNEL);
	if (!bss)
		return -ENOMEM;

	cfg80211_put_bss(wiphy, bss);

	return 0;
}
Esempio n. 15
0
static int
qtnf_event_handle_bss_join(struct qtnf_vif *vif,
			   const struct qlink_event_bss_join *join_info,
			   u16 len)
{
	struct wiphy *wiphy = priv_to_wiphy(vif->mac);
	enum ieee80211_statuscode status = le16_to_cpu(join_info->status);
	struct cfg80211_chan_def chandef;
	struct cfg80211_bss *bss = NULL;
	u8 *ie = NULL;
	size_t payload_len;
	u16 tlv_type;
	u16 tlv_value_len;
	size_t tlv_full_len;
	const struct qlink_tlv_hdr *tlv;
	const u8 *rsp_ies = NULL;
	size_t rsp_ies_len = 0;

	if (unlikely(len < sizeof(*join_info))) {
		pr_err("VIF%u.%u: payload is too short (%u < %zu)\n",
		       vif->mac->macid, vif->vifid, len,
		       sizeof(struct qlink_event_bss_join));
		return -EINVAL;
	}

	if (vif->wdev.iftype != NL80211_IFTYPE_STATION) {
		pr_err("VIF%u.%u: BSS_JOIN event when not in STA mode\n",
		       vif->mac->macid, vif->vifid);
		return -EPROTO;
	}

	pr_debug("VIF%u.%u: BSSID:%pM status:%u\n",
		 vif->mac->macid, vif->vifid, join_info->bssid, status);

	if (status != WLAN_STATUS_SUCCESS)
		goto done;

	qlink_chandef_q2cfg(wiphy, &join_info->chan, &chandef);
	if (!cfg80211_chandef_valid(&chandef)) {
		pr_warn("MAC%u.%u: bad channel freq=%u cf1=%u cf2=%u bw=%u\n",
			vif->mac->macid, vif->vifid,
			chandef.chan->center_freq,
			chandef.center_freq1,
			chandef.center_freq2,
			chandef.width);
		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
		goto done;
	}

	bss = cfg80211_get_bss(wiphy, chandef.chan, join_info->bssid,
			       NULL, 0, IEEE80211_BSS_TYPE_ESS,
			       IEEE80211_PRIVACY_ANY);
	if (!bss) {
		pr_warn("VIF%u.%u: add missing BSS:%pM chan:%u\n",
			vif->mac->macid, vif->vifid,
			join_info->bssid, chandef.chan->hw_value);

		if (!vif->wdev.ssid_len) {
			pr_warn("VIF%u.%u: SSID unknown for BSS:%pM\n",
				vif->mac->macid, vif->vifid,
				join_info->bssid);
			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
			goto done;
		}

		ie = kzalloc(2 + vif->wdev.ssid_len, GFP_KERNEL);
		if (!ie) {
			pr_warn("VIF%u.%u: IE alloc failed for BSS:%pM\n",
				vif->mac->macid, vif->vifid,
				join_info->bssid);
			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
			goto done;
		}

		ie[0] = WLAN_EID_SSID;
		ie[1] = vif->wdev.ssid_len;
		memcpy(ie + 2, vif->wdev.ssid, vif->wdev.ssid_len);

		bss = cfg80211_inform_bss(wiphy, chandef.chan,
					  CFG80211_BSS_FTYPE_UNKNOWN,
					  join_info->bssid, 0,
					  WLAN_CAPABILITY_ESS, 100,
					  ie, 2 + vif->wdev.ssid_len,
					  0, GFP_KERNEL);
		if (!bss) {
			pr_warn("VIF%u.%u: can't connect to unknown BSS: %pM\n",
				vif->mac->macid, vif->vifid,
				join_info->bssid);
			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
			goto done;
		}
	}

	payload_len = len - sizeof(*join_info);
	tlv = (struct qlink_tlv_hdr *)join_info->ies;

	while (payload_len >= sizeof(struct qlink_tlv_hdr)) {
		tlv_type = le16_to_cpu(tlv->type);
		tlv_value_len = le16_to_cpu(tlv->len);
		tlv_full_len = tlv_value_len + sizeof(struct qlink_tlv_hdr);

		if (payload_len < tlv_full_len) {
			pr_warn("invalid %u TLV\n", tlv_type);
			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
			goto done;
		}

		if (tlv_type == QTN_TLV_ID_IE_SET) {
			const struct qlink_tlv_ie_set *ie_set;
			unsigned int ie_len;

			if (payload_len < sizeof(*ie_set)) {
				pr_warn("invalid IE_SET TLV\n");
				status = WLAN_STATUS_UNSPECIFIED_FAILURE;
				goto done;
			}

			ie_set = (const struct qlink_tlv_ie_set *)tlv;
			ie_len = tlv_value_len -
				(sizeof(*ie_set) - sizeof(ie_set->hdr));

			switch (ie_set->type) {
			case QLINK_IE_SET_ASSOC_RESP:
				if (ie_len) {
					rsp_ies = ie_set->ie_data;
					rsp_ies_len = ie_len;
				}
				break;
			default:
				pr_warn("unexpected IE type: %u\n",
					ie_set->type);
				break;
			}
		}

		payload_len -= tlv_full_len;
		tlv = (struct qlink_tlv_hdr *)(tlv->val + tlv_value_len);
	}

	if (payload_len)
		pr_warn("VIF%u.%u: unexpected remaining payload: %zu\n",
			vif->mac->macid, vif->vifid, payload_len);

done:
	cfg80211_connect_result(vif->netdev, join_info->bssid, NULL, 0, rsp_ies,
				rsp_ies_len, status, GFP_KERNEL);
	if (bss) {
		if (!ether_addr_equal(vif->bssid, join_info->bssid))
			ether_addr_copy(vif->bssid, join_info->bssid);
		cfg80211_put_bss(wiphy, bss);
	}

	if (status == WLAN_STATUS_SUCCESS)
		netif_carrier_on(vif->netdev);

	kfree(ie);
	return 0;
}