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

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

net: qualcomm: rmnet: Workaround for HW checksum error



When processing a coalesced frame with only a single packet, the error
bitmask can be incorrect in certain circumstatnce. When the coalescing
frame is closed due to the hardware hitting the packet limit, the byte
limit, the time limit, or encountered an EOF during DMA, the checksum will
always be marked is valid.

In the case where we receive such a frame, we need to checksum the packet
ourselves to determine if the checksum is actually valid.

Change-Id: Ia14c627231731f5484609fd135549ed2848a88d4
Signed-off-by: default avatarSean Tranchetti <stranche@codeaurora.org>
parent 28b0ded1
Loading
Loading
Loading
Loading
+42 −0
Original line number Diff line number Diff line
@@ -620,6 +620,33 @@ static void __rmnet_frag_segment_data(struct rmnet_frag_descriptor *coal_desc,
	list_add_tail(&new_frag->list, list);
}

static bool rmnet_frag_validate_csum(struct rmnet_frag_descriptor *frag_desc)
{
	u8 *data = rmnet_frag_data_ptr(frag_desc);
	unsigned int datagram_len;
	__wsum csum;
	__sum16 pseudo;

	datagram_len = skb_frag_size(&frag_desc->frag) - frag_desc->ip_len;
	if (frag_desc->ip_proto == 4) {
		struct iphdr *iph = (struct iphdr *)data;

		pseudo = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
					    datagram_len,
					    frag_desc->trans_proto, 0);
	} else {
		struct ipv6hdr *ip6h = (struct ipv6hdr *)data;

		pseudo = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
					  datagram_len, frag_desc->trans_proto,
					  0);
	}

	csum = csum_partial(data + frag_desc->ip_len, datagram_len,
			    csum_unfold(pseudo));
	return !csum_fold(csum);
}

/* Converts the coalesced frame into a list of descriptors.
 * NLOs containing csum erros will not be included.
 */
@@ -699,6 +726,21 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
	}

	coal_desc->hdrs_valid = 1;

	if (rmnet_map_v5_csum_buggy(coal_hdr)) {
		/* Mark the checksum as valid if it checks out */
		if (rmnet_frag_validate_csum(coal_desc))
			coal_desc->csum_valid = true;

		coal_desc->hdr_ptr = rmnet_frag_data_ptr(coal_desc);
		coal_desc->gso_size = ntohs(coal_hdr->nl_pairs[0].pkt_len);
		coal_desc->gso_size -= coal_desc->ip_len + coal_desc->trans_len;
		coal_desc->gso_segs = coal_hdr->nl_pairs[0].num_packets;
		list_add_tail(&coal_desc->list, list);
		return;
	}

	/* Segment the coalesced descriptor into new packets */
	for (nlo = 0; nlo < coal_hdr->num_nlos; nlo++) {
		pkt_len = ntohs(coal_hdr->nl_pairs[nlo].pkt_len);
		pkt_len -= coal_desc->ip_len + coal_desc->trans_len;
+1 −0
Original line number Diff line number Diff line
@@ -253,6 +253,7 @@ int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len);
void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
				      struct net_device *orig_dev,
				      int csum_type);
bool rmnet_map_v5_csum_buggy(struct rmnet_map_v5_coal_header *coal_hdr);
int rmnet_map_process_next_hdr_packet(struct sk_buff *skb,
				      struct sk_buff_head *list,
				      u16 len);
+62 −0
Original line number Diff line number Diff line
@@ -542,6 +542,29 @@ void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
	}
}

bool rmnet_map_v5_csum_buggy(struct rmnet_map_v5_coal_header *coal_hdr)
{
	/* Only applies to frames with a single packet */
	if (coal_hdr->num_nlos != 1 || coal_hdr->nl_pairs[0].num_packets != 1)
		return false;

	/* TCP header has FIN or PUSH set */
	if (coal_hdr->close_type == RMNET_MAP_COAL_CLOSE_COAL)
		return true;

	/* Hit packet limit, byte limit, or time limit/EOF on DMA */
	if (coal_hdr->close_type == RMNET_MAP_COAL_CLOSE_HW) {
		switch (coal_hdr->close_value) {
		case RMNET_MAP_COAL_CLOSE_HW_PKT:
		case RMNET_MAP_COAL_CLOSE_HW_BYTE:
		case RMNET_MAP_COAL_CLOSE_HW_TIME:
			return true;
		}
	}

	return false;
}

static void rmnet_map_move_headers(struct sk_buff *skb)
{
	struct iphdr *iph;
@@ -734,6 +757,34 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
	coal_meta->pkt_count = 0;
}

static bool rmnet_map_validate_csum(struct sk_buff *skb,
				    struct rmnet_map_coal_metadata *meta)
{
	u8 *data = rmnet_map_data_ptr(skb);
	unsigned int datagram_len;
	__wsum csum;
	__sum16 pseudo;

	datagram_len = skb->len - meta->ip_len;
	if (meta->ip_proto == 4) {
		struct iphdr *iph = (struct iphdr *)data;

		pseudo = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
					    datagram_len,
					    meta->trans_proto, 0);
	} else {
		struct ipv6hdr *ip6h = (struct ipv6hdr *)data;

		pseudo = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
					  datagram_len, meta->trans_proto,
					  0);
	}

	csum = skb_checksum(skb, meta->ip_len, datagram_len,
			    csum_unfold(pseudo));
	return !csum_fold(csum);
}

/* Converts the coalesced SKB into a list of SKBs.
 * NLOs containing csum erros will not be included.
 * The original coalesced SKB should be treated as invalid and
@@ -817,6 +868,17 @@ static void rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
		return;
	}

	if (rmnet_map_v5_csum_buggy(coal_hdr)) {
		rmnet_map_move_headers(coal_skb);
		/* Mark as valid if it checks out */
		if (rmnet_map_validate_csum(coal_skb, &coal_meta))
			coal_skb->ip_summed = CHECKSUM_UNNECESSARY;

		__skb_queue_tail(list, coal_skb);
		return;
	}

	/* Segment the coalesced SKB into new packets */
	for (nlo = 0; nlo < coal_hdr->num_nlos; nlo++) {
		pkt_len = ntohs(coal_hdr->nl_pairs[nlo].pkt_len);
		pkt_len -= coal_meta.ip_len + coal_meta.trans_len;