static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb)
{
	struct sk_buff *skb = *pskb;
	struct net_device *dev = skb->dev;
	struct macsec_eth_header *hdr;
	struct macsec_secy *secy = NULL;
	struct macsec_rx_sc *rx_sc;
	struct macsec_rx_sa *rx_sa;
	struct macsec_rxh_data *rxd;
	struct macsec_dev *macsec;
	sci_t sci;
	u32 pn;
	bool cbit;
	struct pcpu_rx_sc_stats *rxsc_stats;
	struct pcpu_secy_stats *secy_stats;
	bool pulled_sci;
	int ret;

	if (skb_headroom(skb) < ETH_HLEN)
		goto drop_direct;

	hdr = macsec_ethhdr(skb);
	if (hdr->eth.h_proto != htons(ETH_P_MACSEC)) {
		handle_not_macsec(skb);

		/* and deliver to the uncontrolled port */
		return RX_HANDLER_PASS;
	}

	skb = skb_unshare(skb, GFP_ATOMIC);
	if (!skb) {
		*pskb = NULL;
		return RX_HANDLER_CONSUMED;
	}

	pulled_sci = pskb_may_pull(skb, macsec_extra_len(true));
	if (!pulled_sci) {
		if (!pskb_may_pull(skb, macsec_extra_len(false)))
			goto drop_direct;
	}

	hdr = macsec_ethhdr(skb);

	/* Frames with a SecTAG that has the TCI E bit set but the C
	 * bit clear are discarded, as this reserved encoding is used
	 * to identify frames with a SecTAG that are not to be
	 * delivered to the Controlled Port.
	 */
	if ((hdr->tci_an & (MACSEC_TCI_C | MACSEC_TCI_E)) == MACSEC_TCI_E)
		return RX_HANDLER_PASS;

	/* now, pull the extra length */
	if (hdr->tci_an & MACSEC_TCI_SC) {
		if (!pulled_sci)
			goto drop_direct;
	}

	/* ethernet header is part of crypto processing */
	skb_push(skb, ETH_HLEN);

	macsec_skb_cb(skb)->has_sci = !!(hdr->tci_an & MACSEC_TCI_SC);
	macsec_skb_cb(skb)->assoc_num = hdr->tci_an & MACSEC_AN_MASK;
	sci = macsec_frame_sci(hdr, macsec_skb_cb(skb)->has_sci);

	rcu_read_lock();
	rxd = macsec_data_rcu(skb->dev);

	list_for_each_entry_rcu(macsec, &rxd->secys, secys) {
		struct macsec_rx_sc *sc = find_rx_sc(&macsec->secy, sci);
		sc = sc ? macsec_rxsc_get(sc) : NULL;

		if (sc) {
			secy = &macsec->secy;
			rx_sc = sc;
			break;
		}
	}

	if (!secy)
		goto nosci;

	dev = secy->netdev;
	macsec = macsec_priv(dev);
	secy_stats = this_cpu_ptr(macsec->stats);
	rxsc_stats = this_cpu_ptr(rx_sc->stats);

	if (!macsec_validate_skb(skb, secy->icv_len)) {
		u64_stats_update_begin(&secy_stats->syncp);
		secy_stats->stats.InPktsBadTag++;
		u64_stats_update_end(&secy_stats->syncp);
		goto drop_nosa;
	}

	rx_sa = macsec_rxsa_get(rx_sc->sa[macsec_skb_cb(skb)->assoc_num]);
	if (!rx_sa) {
		/* 10.6.1 if the SA is not in use */

		/* If validateFrames is Strict or the C bit in the
		 * SecTAG is set, discard
		 */
		if (hdr->tci_an & MACSEC_TCI_C ||
		    secy->validate_frames == MACSEC_VALIDATE_STRICT) {
			u64_stats_update_begin(&rxsc_stats->syncp);
			rxsc_stats->stats.InPktsNotUsingSA++;
			u64_stats_update_end(&rxsc_stats->syncp);
			goto drop_nosa;
		}

		/* not Strict, the frame (with the SecTAG and ICV
		 * removed) is delivered to the Controlled Port.
		 */
		u64_stats_update_begin(&rxsc_stats->syncp);
		rxsc_stats->stats.InPktsUnusedSA++;
		u64_stats_update_end(&rxsc_stats->syncp);
		goto deliver;
	}

	/* First, PN check to avoid decrypting obviously wrong packets */
	pn = ntohl(hdr->packet_number);
	if (secy->replay_protect) {
		bool late;

		spin_lock(&rx_sa->lock);
		late = rx_sa->next_pn >= secy->replay_window &&
		       pn < (rx_sa->next_pn - secy->replay_window);
		spin_unlock(&rx_sa->lock);

		if (late) {
			u64_stats_update_begin(&rxsc_stats->syncp);
			rxsc_stats->stats.InPktsLate++;
			u64_stats_update_end(&rxsc_stats->syncp);
			goto drop;
		}
	}

	macsec_skb_cb(skb)->rx_sa = rx_sa;

	/* Disabled && !changed text => skip validation */
	if (hdr->tci_an & MACSEC_TCI_C ||
	    secy->validate_frames != MACSEC_VALIDATE_DISABLED)
		skb = macsec_decrypt(skb, dev, rx_sa, sci, secy);

	if (IS_ERR(skb)) {
		/* the decrypt callback needs the reference */
		if (PTR_ERR(skb) != -EINPROGRESS) {
			macsec_rxsa_put(rx_sa);
			macsec_rxsc_put(rx_sc);
		}
		rcu_read_unlock();
		*pskb = NULL;
		return RX_HANDLER_CONSUMED;
	}

	if (!macsec_post_decrypt(skb, secy, pn))
		goto drop;

deliver:
	macsec_finalize_skb(skb, secy->icv_len,
			    macsec_extra_len(macsec_skb_cb(skb)->has_sci));
	macsec_reset_skb(skb, secy->netdev);

	if (rx_sa)
		macsec_rxsa_put(rx_sa);
	macsec_rxsc_put(rx_sc);

	ret = gro_cells_receive(&macsec->gro_cells, skb);
	if (ret == NET_RX_SUCCESS)
		count_rx(dev, skb->len);
	else
		macsec->secy.netdev->stats.rx_dropped++;

	rcu_read_unlock();

	*pskb = NULL;
	return RX_HANDLER_CONSUMED;

drop:
	macsec_rxsa_put(rx_sa);
drop_nosa:
	macsec_rxsc_put(rx_sc);
	rcu_read_unlock();
drop_direct:
	kfree_skb(skb);
	*pskb = NULL;
	return RX_HANDLER_CONSUMED;

nosci:
	/* 10.6.1 if the SC is not found */
	cbit = !!(hdr->tci_an & MACSEC_TCI_C);
	if (!cbit)
		macsec_finalize_skb(skb, DEFAULT_ICV_LEN,
				    macsec_extra_len(macsec_skb_cb(skb)->has_sci));

	list_for_each_entry_rcu(macsec, &rxd->secys, secys) {
		struct sk_buff *nskb;

		secy_stats = this_cpu_ptr(macsec->stats);

		/* If validateFrames is Strict or the C bit in the
		 * SecTAG is set, discard
		 */
		if (cbit ||
		    macsec->secy.validate_frames == MACSEC_VALIDATE_STRICT) {
			u64_stats_update_begin(&secy_stats->syncp);
			secy_stats->stats.InPktsNoSCI++;
			u64_stats_update_end(&secy_stats->syncp);
			continue;
		}

		/* not strict, the frame (with the SecTAG and ICV
		 * removed) is delivered to the Controlled Port.
		 */
		nskb = skb_clone(skb, GFP_ATOMIC);
		if (!nskb)
			break;

		macsec_reset_skb(nskb, macsec->secy.netdev);

		ret = netif_rx(nskb);
		if (ret == NET_RX_SUCCESS) {
			u64_stats_update_begin(&secy_stats->syncp);
			secy_stats->stats.InPktsUnknownSCI++;
			u64_stats_update_end(&secy_stats->syncp);
		} else {
			macsec->secy.netdev->stats.rx_dropped++;
		}
	}

	rcu_read_unlock();
	*pskb = skb;
	return RX_HANDLER_PASS;
}