static int fwnet_incoming_packet()

in net.c [565:707]


static int fwnet_incoming_packet(struct fwnet_device *dev, __be32 *buf, int len,
				 int source_node_id, int generation,
				 bool is_broadcast)
{
	struct sk_buff *skb;
	struct net_device *net = dev->netdev;
	struct rfc2734_header hdr;
	unsigned lf;
	unsigned long flags;
	struct fwnet_peer *peer;
	struct fwnet_partial_datagram *pd;
	int fg_off;
	int dg_size;
	u16 datagram_label;
	int retval;
	u16 ether_type;

	if (len <= RFC2374_UNFRAG_HDR_SIZE)
		return 0;

	hdr.w0 = be32_to_cpu(buf[0]);
	lf = fwnet_get_hdr_lf(&hdr);
	if (lf == RFC2374_HDR_UNFRAG) {
		/*
		 * An unfragmented datagram has been received by the ieee1394
		 * bus. Build an skbuff around it so we can pass it to the
		 * high level network layer.
		 */
		ether_type = fwnet_get_hdr_ether_type(&hdr);
		buf++;
		len -= RFC2374_UNFRAG_HDR_SIZE;

		skb = dev_alloc_skb(len + LL_RESERVED_SPACE(net));
		if (unlikely(!skb)) {
			net->stats.rx_dropped++;

			return -ENOMEM;
		}
		skb_reserve(skb, LL_RESERVED_SPACE(net));
		skb_put_data(skb, buf, len);

		return fwnet_finish_incoming_packet(net, skb, source_node_id,
						    is_broadcast, ether_type);
	}

	/* A datagram fragment has been received, now the fun begins. */

	if (len <= RFC2374_FRAG_HDR_SIZE)
		return 0;

	hdr.w1 = ntohl(buf[1]);
	buf += 2;
	len -= RFC2374_FRAG_HDR_SIZE;
	if (lf == RFC2374_HDR_FIRSTFRAG) {
		ether_type = fwnet_get_hdr_ether_type(&hdr);
		fg_off = 0;
	} else {
		ether_type = 0;
		fg_off = fwnet_get_hdr_fg_off(&hdr);
	}
	datagram_label = fwnet_get_hdr_dgl(&hdr);
	dg_size = fwnet_get_hdr_dg_size(&hdr);

	if (fg_off + len > dg_size)
		return 0;

	spin_lock_irqsave(&dev->lock, flags);

	peer = fwnet_peer_find_by_node_id(dev, source_node_id, generation);
	if (!peer) {
		retval = -ENOENT;
		goto fail;
	}

	pd = fwnet_pd_find(peer, datagram_label);
	if (pd == NULL) {
		while (peer->pdg_size >= FWNET_MAX_FRAGMENTS) {
			/* remove the oldest */
			fwnet_pd_delete(list_first_entry(&peer->pd_list,
				struct fwnet_partial_datagram, pd_link));
			peer->pdg_size--;
		}
		pd = fwnet_pd_new(net, peer, datagram_label,
				  dg_size, buf, fg_off, len);
		if (pd == NULL) {
			retval = -ENOMEM;
			goto fail;
		}
		peer->pdg_size++;
	} else {
		if (fwnet_frag_overlap(pd, fg_off, len) ||
		    pd->datagram_size != dg_size) {
			/*
			 * Differing datagram sizes or overlapping fragments,
			 * discard old datagram and start a new one.
			 */
			fwnet_pd_delete(pd);
			pd = fwnet_pd_new(net, peer, datagram_label,
					  dg_size, buf, fg_off, len);
			if (pd == NULL) {
				peer->pdg_size--;
				retval = -ENOMEM;
				goto fail;
			}
		} else {
			if (!fwnet_pd_update(peer, pd, buf, fg_off, len)) {
				/*
				 * Couldn't save off fragment anyway
				 * so might as well obliterate the
				 * datagram now.
				 */
				fwnet_pd_delete(pd);
				peer->pdg_size--;
				retval = -ENOMEM;
				goto fail;
			}
		}
	} /* new datagram or add to existing one */

	if (lf == RFC2374_HDR_FIRSTFRAG)
		pd->ether_type = ether_type;

	if (fwnet_pd_is_complete(pd)) {
		ether_type = pd->ether_type;
		peer->pdg_size--;
		skb = skb_get(pd->skb);
		fwnet_pd_delete(pd);

		spin_unlock_irqrestore(&dev->lock, flags);

		return fwnet_finish_incoming_packet(net, skb, source_node_id,
						    false, ether_type);
	}
	/*
	 * Datagram is not complete, we're done for the
	 * moment.
	 */
	retval = 0;
 fail:
	spin_unlock_irqrestore(&dev->lock, flags);

	return retval;
}