struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
				     struct ieee80211_txq *txq)
{
	struct ieee80211_local *local = hw_to_local(hw);
	struct txq_info *txqi = container_of(txq, struct txq_info, txq);
	struct ieee80211_hdr *hdr;
	struct sk_buff *skb = NULL;
	struct fq *fq = &local->fq;
	struct fq_tin *tin = &txqi->tin;
	struct ieee80211_tx_info *info;
	struct ieee80211_tx_data tx;
	ieee80211_tx_result r;
	struct ieee80211_vif *vif = txq->vif;

	WARN_ON_ONCE(softirq_count() == 0);

	if (!ieee80211_txq_airtime_check(hw, txq))
		return NULL;

begin:
	spin_lock_bh(&fq->lock);

	if (test_bit(IEEE80211_TXQ_STOP, &txqi->flags) ||
	    test_bit(IEEE80211_TXQ_STOP_NETIF_TX, &txqi->flags))
		goto out;

	if (vif->txqs_stopped[txq->ac]) {
		set_bit(IEEE80211_TXQ_STOP_NETIF_TX, &txqi->flags);
		goto out;
	}

	/* Make sure fragments stay together. */
	skb = __skb_dequeue(&txqi->frags);
	if (unlikely(skb)) {
		if (!(IEEE80211_SKB_CB(skb)->control.flags &
				IEEE80211_TX_INTCFL_NEED_TXPROCESSING))
			goto out;
		IEEE80211_SKB_CB(skb)->control.flags &=
			~IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
	} else {
		skb = fq_tin_dequeue(fq, tin, fq_tin_dequeue_func);
	}

	if (!skb)
		goto out;

	spin_unlock_bh(&fq->lock);

	hdr = (struct ieee80211_hdr *)skb->data;
	info = IEEE80211_SKB_CB(skb);

	memset(&tx, 0, sizeof(tx));
	__skb_queue_head_init(&tx.skbs);
	tx.local = local;
	tx.skb = skb;
	tx.sdata = vif_to_sdata(info->control.vif);

	if (txq->sta) {
		tx.sta = container_of(txq->sta, struct sta_info, sta);
		/*
		 * Drop unicast frames to unauthorised stations unless they are
		 * injected frames or EAPOL frames from the local station.
		 */
		if (unlikely(!(info->flags & IEEE80211_TX_CTL_INJECTED) &&
			     ieee80211_is_data(hdr->frame_control) &&
			     !ieee80211_vif_is_mesh(&tx.sdata->vif) &&
			     tx.sdata->vif.type != NL80211_IFTYPE_OCB &&
			     !is_multicast_ether_addr(hdr->addr1) &&
			     !test_sta_flag(tx.sta, WLAN_STA_AUTHORIZED) &&
			     (!(info->control.flags &
				IEEE80211_TX_CTRL_PORT_CTRL_PROTO) ||
			      !ether_addr_equal(tx.sdata->vif.addr,
						hdr->addr2)))) {
			I802_DEBUG_INC(local->tx_handlers_drop_unauth_port);
			ieee80211_free_txskb(&local->hw, skb);
			goto begin;
		}
	}

	/*
	 * The key can be removed while the packet was queued, so need to call
	 * this here to get the current key.
	 */
	r = ieee80211_tx_h_select_key(&tx);
	if (r != TX_CONTINUE) {
		ieee80211_free_txskb(&local->hw, skb);
		goto begin;
	}

	if (test_bit(IEEE80211_TXQ_AMPDU, &txqi->flags))
		info->flags |= IEEE80211_TX_CTL_AMPDU;
	else
		info->flags &= ~IEEE80211_TX_CTL_AMPDU;

	if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)
		goto encap_out;

	if (info->control.flags & IEEE80211_TX_CTRL_FAST_XMIT) {
		struct sta_info *sta = container_of(txq->sta, struct sta_info,
						    sta);
		u8 pn_offs = 0;

		if (tx.key &&
		    (tx.key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV))
			pn_offs = ieee80211_hdrlen(hdr->frame_control);

		ieee80211_xmit_fast_finish(sta->sdata, sta, pn_offs,
					   tx.key, skb);
	} else {
		if (invoke_tx_handlers_late(&tx))
			goto begin;

		skb = __skb_dequeue(&tx.skbs);

		if (!skb_queue_empty(&tx.skbs)) {
			spin_lock_bh(&fq->lock);
			skb_queue_splice_tail(&tx.skbs, &txqi->frags);
			spin_unlock_bh(&fq->lock);
		}
	}

	if (skb_has_frag_list(skb) &&
	    !ieee80211_hw_check(&local->hw, TX_FRAG_LIST)) {
		if (skb_linearize(skb)) {
			ieee80211_free_txskb(&local->hw, skb);
			goto begin;
		}
	}

	switch (tx.sdata->vif.type) {
	case NL80211_IFTYPE_MONITOR:
		if (tx.sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) {
			vif = &tx.sdata->vif;
			break;
		}
		tx.sdata = rcu_dereference(local->monitor_sdata);
		if (tx.sdata) {
			vif = &tx.sdata->vif;
			info->hw_queue =
				vif->hw_queue[skb_get_queue_mapping(skb)];
		} else if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL)) {
			ieee80211_free_txskb(&local->hw, skb);
			goto begin;
		} else {
			vif = NULL;
		}
		break;
	case NL80211_IFTYPE_AP_VLAN:
		tx.sdata = container_of(tx.sdata->bss,
					struct ieee80211_sub_if_data, u.ap);
		fallthrough;
	default:
		vif = &tx.sdata->vif;
		break;
	}

encap_out:
	IEEE80211_SKB_CB(skb)->control.vif = vif;

	if (vif &&
	    wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) {
		bool ampdu = txq->ac != IEEE80211_AC_VO;
		u32 airtime;

		airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta,
							     skb->len, ampdu);
		if (airtime) {
			airtime = ieee80211_info_set_tx_time_est(info, airtime);
			ieee80211_sta_update_pending_airtime(local, tx.sta,
							     txq->ac,
							     airtime,
							     false);
		}
	}

	return skb;

out:
	spin_unlock_bh(&fq->lock);

	return skb;
}