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;
}