Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 55c9e5be authored by Sean Tranchetti's avatar Sean Tranchetti Committed by Gerrit - the friendly Code Review server
Browse files

net: qualcomm: rmnet: Segmentation cleanup



There is a lot of repeated code in the various segmentation functions.
Introduce and use a struct to hold various pieces of metadata we'll need
later on during the segmentation process like the lengths and positions of
headers. Passing this struct allows us to only compute various values once
instead of in every function, and cleans up the parameter lists.

Change-Id: I19d36701573c818ddbd8d3f8d1c1ace717712bec
Signed-off-by: default avatarSean Tranchetti <stranche@codeaurora.org>
parent 7bc951b1
Loading
Loading
Loading
Loading
+124 −213
Original line number Diff line number Diff line
@@ -18,6 +18,19 @@
#define RMNET_MAP_DEAGGR_SPACING  64
#define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2)

struct rmnet_map_coal_metadata {
	void *ip_header;
	void *trans_header;
	u16 ip_len;
	u16 trans_len;
	u16 data_offset;
	u16 data_len;
	u8 ip_proto;
	u8 trans_proto;
	u8 pkt_id;
	u8 pkt_count;
};

static __sum16 *rmnet_map_get_csum_field(unsigned char protocol,
					 const void *txporthdr)
{
@@ -530,143 +543,125 @@ void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
}

static void rmnet_map_nonlinear_copy(struct sk_buff *coal_skb,
				     u32 hdr_len,
				     u32 start,
				     u16 pkt_len, u8 pkt_count,
				     struct rmnet_map_coal_metadata *coal_meta,
				     struct sk_buff *dest)
{
	unsigned char *data_start = rmnet_map_data_ptr(coal_skb) + hdr_len;
	u32 copy_len = pkt_len * pkt_count;
	unsigned char *data_start = rmnet_map_data_ptr(coal_skb) +
				    coal_meta->ip_len + coal_meta->trans_len;
	u32 copy_len = coal_meta->data_len * coal_meta->pkt_count;

	if (skb_is_nonlinear(coal_skb)) {
		skb_frag_t *frag0 = skb_shinfo(coal_skb)->frags;
		struct page *page = skb_frag_page(frag0);

		skb_append_pagefrags(dest, page,
				     frag0->page_offset + hdr_len + start,
				     frag0->page_offset + coal_meta->ip_len +
				     coal_meta->trans_len +
				     coal_meta->data_offset,
				     copy_len);
		dest->data_len += copy_len;
		dest->len += copy_len;
	} else {
		skb_put_data(dest, data_start + start, copy_len);
		skb_put_data(dest, data_start + coal_meta->data_offset,
			     copy_len);
	}
}

/* Fill in GSO metadata to allow the SKB to be segmented by the NW stack
 * if needed (i.e. forwarding, UDP GRO)
 */
static void rmnet_map_gso_stamp(struct sk_buff *skb, u16 gso_size, u8 gso_segs)
static void rmnet_map_gso_stamp(struct sk_buff *skb,
				struct rmnet_map_coal_metadata *coal_meta)
{
	struct skb_shared_info *shinfo = skb_shinfo(skb);
	struct iphdr *iph = ip_hdr(skb);
	void *addr;
	__sum16 *check;
	__wsum partial;
	int csum_len;
	u16 pkt_len = gso_size * gso_segs;
	u8 protocol;
	bool ipv4 = iph->version == 4;
	__sum16 pseudo;
	u16 pkt_len = skb->len - coal_meta->ip_len;
	bool ipv4 = coal_meta->ip_proto == 4;

	if (ipv4) {
		addr = &iph->saddr;
		csum_len = sizeof(iph->saddr) * 2;
		protocol = iph->protocol;
		pseudo = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
					    pkt_len, coal_meta->trans_proto,
					    0);
	} else {
		struct ipv6hdr *ip6h = ipv6_hdr(skb);

		addr = &ip6h->saddr;
		csum_len = sizeof(ip6h->saddr) * 2;
		protocol = ip6h->nexthdr;
		pseudo = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
					  pkt_len, coal_meta->trans_proto, 0);
	}

	if (protocol == IPPROTO_TCP) {
	if (coal_meta->trans_proto == IPPROTO_TCP) {
		struct tcphdr *tp = tcp_hdr(skb);

		pkt_len += tp->doff * 4;
		check = &tp->check;
		tp->check = pseudo;
		shinfo->gso_type = (ipv4) ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
		skb->csum_offset = offsetof(struct tcphdr, check);
	} else {
		struct udphdr *up = udp_hdr(skb);

		pkt_len += sizeof(*up);
		check = &up->check;
		up->check = pseudo;
		shinfo->gso_type = SKB_GSO_UDP_L4;
		skb->csum_offset = offsetof(struct udphdr, check);
	}

	partial = csum_partial(addr, csum_len, 0);
	partial = csum16_add(partial, htons((u16)protocol));
	partial = csum16_add(partial, htons(pkt_len));
	*check = ~csum_fold(partial);

	skb->ip_summed = CHECKSUM_PARTIAL;
	skb->csum_start = skb_transport_header(skb) - skb->head;
	shinfo->gso_size = gso_size;
	shinfo->gso_segs = gso_segs;
	shinfo->gso_size = coal_meta->data_len;
	shinfo->gso_segs = coal_meta->pkt_count;
}

/* Create a new UDP SKB from the coalesced SKB. Appropriate IP and UDP headers
 * will be added.
 */
static struct sk_buff *rmnet_map_segment_udp_skb(struct sk_buff *coal_skb,
						 u32 start,
						 int start_pkt_num,
						 u16 pkt_len, u8 pkt_count)
static void
__rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
			     struct rmnet_map_coal_metadata *coal_meta,
			     struct sk_buff_head *list, u8 pkt_id)
{
	struct sk_buff *skbn;
	struct iphdr *iph = (struct iphdr *)rmnet_map_data_ptr(coal_skb);
	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
	struct udphdr *uh;
	u32 alloc_len;
	u16 ip_len, udp_len = sizeof(*uh);

	if (iph->version == 4) {
		ip_len = iph->ihl * 4;
	} else if (iph->version == 6) {
		ip_len = sizeof(struct ipv6hdr);
	} else {
		priv->stats.coal.coal_ip_invalid++;
		return NULL;
	}

	uh = (struct udphdr *)(rmnet_map_data_ptr(coal_skb) + ip_len);

	/* We can avoid copying the data if the SKB we got from the lower-level
	 * drivers was nonlinear.
	 */
	if (skb_is_nonlinear(coal_skb))
		alloc_len = ip_len + udp_len;
		alloc_len = coal_meta->ip_len + coal_meta->trans_len;
	else
		alloc_len = ip_len + udp_len + pkt_len;
		alloc_len = coal_meta->ip_len + coal_meta->trans_len +
			    coal_meta->data_len;

	skbn = alloc_skb(alloc_len, GFP_ATOMIC);
	if (!skbn)
		return NULL;
		return;

	skb_reserve(skbn, ip_len + udp_len);
	rmnet_map_nonlinear_copy(coal_skb, ip_len + udp_len,
				 start, pkt_len, pkt_count, skbn);
	skb_reserve(skbn, coal_meta->ip_len + coal_meta->trans_len);
	rmnet_map_nonlinear_copy(coal_skb, coal_meta, skbn);

	/* Push UDP header and update length */
	skb_push(skbn, udp_len);
	memcpy(skbn->data, uh, udp_len);
	/* Push transport header and update necessary fields */
	skb_push(skbn, coal_meta->trans_len);
	memcpy(skbn->data, coal_meta->trans_header, coal_meta->trans_len);
	skb_reset_transport_header(skbn);
	if (coal_meta->trans_proto == IPPROTO_TCP) {
		struct tcphdr *th = tcp_hdr(skbn);

		th->seq = htonl(ntohl(th->seq) + coal_meta->data_offset);
	} else if (coal_meta->trans_proto == IPPROTO_UDP) {
		udp_hdr(skbn)->len = htons(skbn->len);
	}

	/* Push IP header and update necessary fields */
	skb_push(skbn, ip_len);
	memcpy(skbn->data, iph, ip_len);
	skb_push(skbn, coal_meta->ip_len);
	memcpy(skbn->data, coal_meta->ip_header, coal_meta->ip_len);
	skb_reset_network_header(skbn);
	if (iph->version == 4) {
		iph = ip_hdr(skbn);
		iph->id = htons(ntohs(iph->id) + start_pkt_num);
	if (coal_meta->ip_proto == 4) {
		struct iphdr *iph = ip_hdr(skbn);

		iph->id = htons(ntohs(iph->id) + coal_meta->pkt_id);
		iph->tot_len = htons(skbn->len);
		iph->check = 0;
		iph->check = ip_fast_csum(iph, iph->ihl);
	} else {
		/* Payload length includes any extension headers */
		ipv6_hdr(skbn)->payload_len = htons(skbn->len -
						    ip_len);
						    sizeof(struct ipv6hdr));
	}

	skbn->ip_summed = CHECKSUM_UNNECESSARY;
@@ -674,85 +669,15 @@ static struct sk_buff *rmnet_map_segment_udp_skb(struct sk_buff *coal_skb,
	priv->stats.coal.coal_reconstruct++;

	/* Stamp GSO information if necessary */
	if (pkt_count > 1)
		rmnet_map_gso_stamp(skbn, pkt_len, pkt_count);
	if (coal_meta->pkt_count > 1)
		rmnet_map_gso_stamp(skbn, coal_meta);

	return skbn;
}

/* Create a new TCP SKB from the coalesced SKB. Appropriate IP and TCP headers
 * will be added.
 */
static struct sk_buff *rmnet_map_segment_tcp_skb(struct sk_buff *coal_skb,
						 u32 start,
						 int start_pkt_num,
						 u16 pkt_len, u8 pkt_count)
{
	struct sk_buff *skbn;
	struct iphdr *iph = (struct iphdr *)rmnet_map_data_ptr(coal_skb);
	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
	struct tcphdr *th;
	u32 alloc_len;
	u16 ip_len, tcp_len;
	__skb_queue_tail(list, skbn);

	if (iph->version == 4) {
		ip_len = iph->ihl * 4;
	} else if (iph->version == 6) {
		ip_len = sizeof(struct ipv6hdr);
	} else {
		priv->stats.coal.coal_ip_invalid++;
		return NULL;
	}

	th = (struct tcphdr *)(rmnet_map_data_ptr(coal_skb) + ip_len);
	tcp_len = th->doff * 4;

	/* We can avoid copying the data if the SKB we got from the lower-level
	 * drivers was nonlinear.
	 */
	if (skb_is_nonlinear(coal_skb))
		alloc_len = ip_len + tcp_len;
	else
		alloc_len = ip_len + tcp_len + pkt_len;

	skbn = alloc_skb(alloc_len, GFP_ATOMIC);
	if (!skbn)
		return NULL;

	skb_reserve(skbn, ip_len + tcp_len);
	rmnet_map_nonlinear_copy(coal_skb, ip_len + tcp_len,
				 start, pkt_len, pkt_count, skbn);

	/* Push TCP header and update sequence number */
	skb_push(skbn, tcp_len);
	memcpy(skbn->data, th, tcp_len);
	skb_reset_transport_header(skbn);
	th = tcp_hdr(skbn);
	th->seq = htonl(ntohl(th->seq) + start);

	/* Push IP header and update necessary fields */
	skb_push(skbn, ip_len);
	memcpy(skbn->data, iph, ip_len);
	skb_reset_network_header(skbn);
	if (iph->version == 4) {
		iph = ip_hdr(skbn);
		iph->id = htons(ntohs(iph->id) + start_pkt_num);
		iph->tot_len = htons(skbn->len);
		iph->check = 0;
		iph->check = ip_fast_csum(iph, iph->ihl);
	} else {
		ipv6_hdr(skbn)->payload_len = htons(skbn->len - ip_len);
	}

	skbn->ip_summed = CHECKSUM_UNNECESSARY;
	skbn->dev = coal_skb->dev;
	priv->stats.coal.coal_reconstruct++;

	/* Stamp GSO information if necessary */
	if (pkt_count > 1)
		rmnet_map_gso_stamp(skbn, pkt_len, pkt_count);

	return skbn;
	/* Update meta information to move past the data we just segmented */
	coal_meta->data_offset += coal_meta->data_len * coal_meta->pkt_count;
	coal_meta->pkt_id = pkt_id + 1;
	coal_meta->pkt_count = 0;
}

/* Converts the coalesced SKB into a list of SKBs.
@@ -760,25 +685,21 @@ static struct sk_buff *rmnet_map_segment_tcp_skb(struct sk_buff *coal_skb,
 * The original coalesced SKB should be treated as invalid and
 * must be freed by the caller
 */
static void rmnet_map_segment_coal_data(struct sk_buff *coal_skb,
static void rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
				       u64 nlo_err_mask,
				       struct sk_buff_head *list)
{
	struct sk_buff *new_skb;
	struct sk_buff *(*segment)(struct sk_buff *coal_skb,
				   u32 start,
				   int start_pkt_num,
				   u16 pkt_len, u8 pkt_count);
	struct iphdr *iph;
	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
	struct rmnet_map_v5_coal_header *coal_hdr;
	u32 start = 0;
	u16 pkt_len, ip_len, trans_len;
	u8 protocol, start_pkt_num = 0;
	struct rmnet_map_coal_metadata coal_meta;
	u16 pkt_len;
	u8 pkt, total_pkt = 0;
	u8 nlo, gro_count = 0;
	u8 nlo;
	bool gro = coal_skb->dev->features & NETIF_F_GRO_HW;

	memset(&coal_meta, 0, sizeof(coal_meta));

	/* Pull off the headers we no longer need */
	pskb_pull(coal_skb, sizeof(struct rmnet_map_header));
	coal_hdr = (struct rmnet_map_v5_coal_header *)
@@ -788,27 +709,33 @@ static void rmnet_map_segment_coal_data(struct sk_buff *coal_skb,
	iph = (struct iphdr *)rmnet_map_data_ptr(coal_skb);

	if (iph->version == 4) {
		protocol = iph->protocol;
		ip_len = iph->ihl * 4;
		coal_meta.ip_proto = 4;
		coal_meta.ip_len = iph->ihl * 4;
		coal_meta.trans_proto = iph->protocol;
		coal_meta.ip_header = iph;

		/* Don't allow coalescing of any packets with IP options */
		if (iph->ihl != 5)
			gro = false;
	} else if (iph->version == 6) {
		struct ipv6hdr *ip6h = (struct ipv6hdr *)iph;
		__be16 frag_off;
		u8 protocol = ip6h->nexthdr;

		protocol = ((struct ipv6hdr *)iph)->nexthdr;
		ip_len = ipv6_skip_exthdr(coal_skb, sizeof(struct ipv6hdr),
		coal_meta.ip_proto = 6;
		coal_meta.ip_len = ipv6_skip_exthdr(coal_skb, sizeof(*ip6h),
						    &protocol, &frag_off);
		coal_meta.trans_proto = protocol;
		coal_meta.ip_header = ip6h;

		/* If we run into a problem, or this has a fragment header
		 * (which should technically not be possible, if the HW
		 * works as intended...), bail.
		 */
		if (ip_len < 0 || frag_off) {
		if (coal_meta.ip_len < 0 || frag_off) {
			priv->stats.coal.coal_ip_invalid++;
			return;
		} else if (ip_len > sizeof(struct ipv6hdr)) {
		} else if (coal_meta.ip_len > sizeof(*ip6h)) {
			/* Don't allow coalescing of any packets with IPv6
			 * extension headers.
			 */
@@ -819,14 +746,18 @@ static void rmnet_map_segment_coal_data(struct sk_buff *coal_skb,
		return;
	}

	if (protocol == IPPROTO_TCP) {
		struct tcphdr *th = (struct tcphdr *)
				    ((unsigned char *)iph + ip_len);
		trans_len = th->doff * 4;
		segment = rmnet_map_segment_tcp_skb;
	} else if (protocol == IPPROTO_UDP) {
		trans_len = sizeof(struct udphdr);
		segment = rmnet_map_segment_udp_skb;
	if (coal_meta.trans_proto == IPPROTO_TCP) {
		struct tcphdr *th;

		th = (struct tcphdr *)((u8 *)iph + coal_meta.ip_len);
		coal_meta.trans_len = th->doff * 4;
		coal_meta.trans_header = th;
	} else if (coal_meta.trans_proto == IPPROTO_UDP) {
		struct udphdr *uh;

		uh = (struct udphdr *)((u8 *)iph + coal_meta.ip_len);
		coal_meta.trans_len = sizeof(*uh);
		coal_meta.trans_header = uh;
	} else {
		priv->stats.coal.coal_trans_invalid++;
		return;
@@ -834,7 +765,8 @@ static void rmnet_map_segment_coal_data(struct sk_buff *coal_skb,

	for (nlo = 0; nlo < coal_hdr->num_nlos; nlo++) {
		pkt_len = ntohs(coal_hdr->nl_pairs[nlo].pkt_len);
		pkt_len -= ip_len + trans_len;
		pkt_len -= coal_meta.ip_len + coal_meta.trans_len;
		coal_meta.data_len = pkt_len;
		for (pkt = 0; pkt < coal_hdr->nl_pairs[nlo].num_packets;
		     pkt++, total_pkt++) {
			nlo_err_mask <<= 1;
@@ -842,40 +774,27 @@ static void rmnet_map_segment_coal_data(struct sk_buff *coal_skb,
				priv->stats.coal.coal_csum_err++;

				/* Segment out the good data */
				if (gro && gro_count) {
					new_skb = segment(coal_skb, start,
							  start_pkt_num,
							  pkt_len, gro_count);
					if (!new_skb)
						return;

					__skb_queue_tail(list, new_skb);
					start += pkt_len * gro_count;
					gro_count = 0;
				if (gro && coal_meta.pkt_count) {
					__rmnet_map_segment_coal_skb(coal_skb,
								     &coal_meta,
								     list,
								     total_pkt);
				}

				/* skip over bad packet */
				start += pkt_len;
				start_pkt_num = total_pkt + 1;
				coal_meta.data_offset += pkt_len;
				coal_meta.pkt_id = total_pkt + 1;
			} else {
				gro_count++;
				coal_meta.pkt_count++;

				/* Segment the packet if we aren't sending the
				 * larger packet up the stack.
				 */
				if (!gro) {
					new_skb = segment(coal_skb, start,
							  start_pkt_num,
							  pkt_len, 1);
					if (!new_skb)
						return;

					__skb_queue_tail(list, new_skb);

					start += pkt_len;
					start_pkt_num = total_pkt + 1;
					gro_count = 0;
				}
				if (!gro)
					__rmnet_map_segment_coal_skb(coal_skb,
								     &coal_meta,
								     list,
								     total_pkt);
			}
		}

@@ -883,18 +802,10 @@ static void rmnet_map_segment_coal_data(struct sk_buff *coal_skb,
		 * the previous one, if we haven't done so. NLOs only switch
		 * when the packet length changes.
		 */
		if (gro && gro_count) {
			new_skb = segment(coal_skb, start, start_pkt_num,
					  pkt_len, gro_count);
			if (!new_skb)
				return;
		if (gro && coal_meta.pkt_count)
			__rmnet_map_segment_coal_skb(coal_skb, &coal_meta, list,
						     total_pkt);

			__skb_queue_tail(list, new_skb);

			start += pkt_len * gro_count;
			start_pkt_num = total_pkt + 1;
			gro_count = 0;
		}
	}
}

@@ -1015,7 +926,7 @@ int rmnet_map_process_next_hdr_packet(struct sk_buff *skb,
		if (rc)
			return rc;

		rmnet_map_segment_coal_data(skb, nlo_err_mask, list);
		rmnet_map_segment_coal_skb(skb, nlo_err_mask, list);
		consume_skb(skb);
		break;
	case RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD: