static int qeth_extract_skb()

in net/qeth_core_main.c [5617:5795]


static int qeth_extract_skb(struct qeth_card *card,
			    struct qeth_qdio_buffer *qethbuffer, u8 *element_no,
			    int *__offset)
{
	struct qeth_priv *priv = netdev_priv(card->dev);
	struct qdio_buffer *buffer = qethbuffer->buffer;
	struct napi_struct *napi = &card->napi;
	struct qdio_buffer_element *element;
	unsigned int linear_len = 0;
	bool uses_frags = false;
	int offset = *__offset;
	bool use_rx_sg = false;
	unsigned int headroom;
	struct qeth_hdr *hdr;
	struct sk_buff *skb;
	int skb_len = 0;
	bool is_cso;

	element = &buffer->element[*element_no];

next_packet:
	/* qeth_hdr must not cross element boundaries */
	while (element->length < offset + sizeof(struct qeth_hdr)) {
		if (qeth_is_last_sbale(element))
			return -ENODATA;
		element++;
		offset = 0;
	}

	hdr = phys_to_virt(element->addr) + offset;
	offset += sizeof(*hdr);
	skb = NULL;

	switch (hdr->hdr.l2.id) {
	case QETH_HEADER_TYPE_LAYER2:
		skb_len = hdr->hdr.l2.pkt_length;
		is_cso = hdr->hdr.l2.flags[1] & QETH_HDR_EXT_CSUM_TRANSP_REQ;

		linear_len = ETH_HLEN;
		headroom = 0;
		break;
	case QETH_HEADER_TYPE_LAYER3:
		skb_len = hdr->hdr.l3.length;
		is_cso = hdr->hdr.l3.ext_flags & QETH_HDR_EXT_CSUM_TRANSP_REQ;

		if (!IS_LAYER3(card)) {
			QETH_CARD_STAT_INC(card, rx_dropped_notsupp);
			goto walk_packet;
		}

		if (hdr->hdr.l3.flags & QETH_HDR_PASSTHRU) {
			linear_len = ETH_HLEN;
			headroom = 0;
			break;
		}

		if (hdr->hdr.l3.flags & QETH_HDR_IPV6)
			linear_len = sizeof(struct ipv6hdr);
		else
			linear_len = sizeof(struct iphdr);
		headroom = ETH_HLEN;
		break;
	default:
		if (hdr->hdr.l2.id & QETH_HEADER_MASK_INVAL)
			QETH_CARD_STAT_INC(card, rx_frame_errors);
		else
			QETH_CARD_STAT_INC(card, rx_dropped_notsupp);

		/* Can't determine packet length, drop the whole buffer. */
		return -EPROTONOSUPPORT;
	}

	if (skb_len < linear_len) {
		QETH_CARD_STAT_INC(card, rx_dropped_runt);
		goto walk_packet;
	}

	use_rx_sg = (card->options.cq == QETH_CQ_ENABLED) ||
		    (skb_len > READ_ONCE(priv->rx_copybreak) &&
		     !atomic_read(&card->force_alloc_skb));

	if (use_rx_sg) {
		/* QETH_CQ_ENABLED only: */
		if (qethbuffer->rx_skb &&
		    skb_tailroom(qethbuffer->rx_skb) >= linear_len + headroom) {
			skb = qethbuffer->rx_skb;
			qethbuffer->rx_skb = NULL;
			goto use_skb;
		}

		skb = napi_get_frags(napi);
		if (!skb) {
			/* -ENOMEM, no point in falling back further. */
			QETH_CARD_STAT_INC(card, rx_dropped_nomem);
			goto walk_packet;
		}

		if (skb_tailroom(skb) >= linear_len + headroom) {
			uses_frags = true;
			goto use_skb;
		}

		netdev_info_once(card->dev,
				 "Insufficient linear space in NAPI frags skb, need %u but have %u\n",
				 linear_len + headroom, skb_tailroom(skb));
		/* Shouldn't happen. Don't optimize, fall back to linear skb. */
	}

	linear_len = skb_len;
	skb = napi_alloc_skb(napi, linear_len + headroom);
	if (!skb) {
		QETH_CARD_STAT_INC(card, rx_dropped_nomem);
		goto walk_packet;
	}

use_skb:
	if (headroom)
		skb_reserve(skb, headroom);
walk_packet:
	while (skb_len) {
		int data_len = min(skb_len, (int)(element->length - offset));
		char *data = phys_to_virt(element->addr) + offset;

		skb_len -= data_len;
		offset += data_len;

		/* Extract data from current element: */
		if (skb && data_len) {
			if (linear_len) {
				unsigned int copy_len;

				copy_len = min_t(unsigned int, linear_len,
						 data_len);

				skb_put_data(skb, data, copy_len);
				linear_len -= copy_len;
				data_len -= copy_len;
				data += copy_len;
			}

			if (data_len)
				qeth_create_skb_frag(skb, data, data_len);
		}

		/* Step forward to next element: */
		if (skb_len) {
			if (qeth_is_last_sbale(element)) {
				QETH_CARD_TEXT(card, 4, "unexeob");
				QETH_CARD_HEX(card, 2, buffer, sizeof(void *));
				if (skb) {
					if (uses_frags)
						napi_free_frags(napi);
					else
						kfree_skb(skb);
					QETH_CARD_STAT_INC(card,
							   rx_length_errors);
				}
				return -EMSGSIZE;
			}
			element++;
			offset = 0;
		}
	}

	/* This packet was skipped, go get another one: */
	if (!skb)
		goto next_packet;

	*element_no = element - &buffer->element[0];
	*__offset = offset;

#if IS_ENABLED(CONFIG_QETH_L3)
	if (hdr->hdr.l2.id == QETH_HEADER_TYPE_LAYER3)
		qeth_l3_rebuild_skb(card, skb, hdr);
#endif

	qeth_receive_skb(card, skb, uses_frags, is_cso);
	return 0;
}