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

Commit 31542ad2 authored by Linux Build Service Account's avatar Linux Build Service Account
Browse files

Merge 916deeb7 on remote branch

Change-Id: I7f6687cd275df56d552b50ae0703c69a3d10a8d7
parents 14fa5001 916deeb7
Loading
Loading
Loading
Loading
+168 −8
Original line number Diff line number Diff line
/* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
 * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -322,12 +323,13 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to,
EXPORT_SYMBOL(rmnet_frag_descriptor_add_frags_from);

int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc,
				int start, u8 *nexthdrp, __be16 *fragp)
				int start, u8 *nexthdrp, __be16 *frag_offp,
				bool *frag_hdrp)
{
	u8 nexthdr = *nexthdrp;

	*fragp = 0;

	*frag_offp = 0;
	*frag_hdrp = false;
	while (ipv6_ext_hdr(nexthdr)) {
		struct ipv6_opt_hdr *hp, __hp;
		int hdrlen;
@@ -349,8 +351,9 @@ int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc,
			if (!fp)
				return -EINVAL;

			*fragp = *fp;
			if (ntohs(*fragp) & ~0x7)
			*frag_offp = *fp;
			*frag_hdrp = true;
			if (ntohs(*frag_offp) & ~0x7)
				break;
			hdrlen = 8;
		} else if (nexthdr == NEXTHDR_AUTH) {
@@ -1143,6 +1146,7 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
		struct ipv6hdr *ip6h, __ip6h;
		int ip_len;
		__be16 frag_off;
		bool frag_hdr;
		u8 protocol;

		ip6h = rmnet_frag_header_ptr(coal_desc, 0, sizeof(*ip6h),
@@ -1155,10 +1159,11 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
		ip_len = rmnet_frag_ipv6_skip_exthdr(coal_desc,
						     sizeof(*ip6h),
						     &protocol,
						     &frag_off);
						     &frag_off,
						     &frag_hdr);
		coal_desc->trans_proto = protocol;

		/* If we run into a problem, or this has a fragment header
		/* If we run into a problem, or this is fragmented packet
		 * (which should technically not be possible, if the HW
		 * works as intended...), bail.
		 */
@@ -1167,6 +1172,14 @@ rmnet_frag_segment_coal_data(struct rmnet_frag_descriptor *coal_desc,
			return;
		}

		if (frag_hdr) {
			/* There is a fragment header, but this is not a
			 * fragmented packet. We can handle this, but it
			 * cannot be coalesced because of kernel limitations.
			 */
			gro = false;
		}

		coal_desc->ip_len = (u16)ip_len;
		if (coal_desc->ip_len > sizeof(*ip6h)) {
			/* Don't allow coalescing of any packets with IPv6
@@ -1396,6 +1409,142 @@ rmnet_frag_data_check_coal_header(struct rmnet_frag_descriptor *frag_desc,
	return 0;
}

static int rmnet_frag_checksum_pkt(struct rmnet_frag_descriptor *frag_desc)
{
	struct rmnet_priv *priv = netdev_priv(frag_desc->dev);
	struct rmnet_fragment *frag;
	int offset = sizeof(struct rmnet_map_header) +
		     sizeof(struct rmnet_map_v5_csum_header);
	u8 *version, __version;
	__wsum csum;
	u16 csum_len;

	version = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*version),
					&__version);
	if (!version)
		return -EINVAL;

	if ((*version & 0xF0) == 0x40) {
		struct iphdr *iph;
		u8 __iph[60]; /* Max IP header size (0xF * 4) */

		/* We need to access the entire IP header including options
		 * to validate its checksum. Fortunately, the version byte
		 * also will tell us the length, so we only need to pull
		 * once ;)
		 */
		frag_desc->ip_len = (*version & 0xF) * 4;
		iph = rmnet_frag_header_ptr(frag_desc, offset,
					    frag_desc->ip_len,
					    __iph);
		if (!iph || ip_is_fragment(iph))
			return -EINVAL;

		/* Length needs to be sensible */
		csum_len = ntohs(iph->tot_len);
		if (csum_len > frag_desc->len - offset)
			return -EINVAL;

		csum_len -= frag_desc->ip_len;
		/* IPv4 checksum must be valid */
		if (ip_fast_csum((u8 *)iph, frag_desc->ip_len)) {
			priv->stats.csum_sw++;
			return 0;
		}

		frag_desc->ip_proto = 4;
		frag_desc->trans_proto = iph->protocol;
		csum = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
					  csum_len,
					  frag_desc->trans_proto, 0);
	} else if ((*version & 0xF0) == 0x60) {
		struct ipv6hdr *ip6h, __ip6h;
		int ip_len;
		__be16 frag_off;
		bool frag_hdr;
		u8 protocol;

		ip6h = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*ip6h),
					     &__ip6h);
		if (!ip6h)
			return -EINVAL;

		frag_desc->ip_proto = 6;
		protocol = ip6h->nexthdr;
		ip_len = rmnet_frag_ipv6_skip_exthdr(frag_desc,
						     offset + sizeof(*ip6h),
						     &protocol, &frag_off,
						     &frag_hdr);
		if (ip_len < 0 || frag_off)
			return -EINVAL;

		/* Length needs to be sensible */
		frag_desc->ip_len = (u16)ip_len;
		csum_len = ntohs(ip6h->payload_len);
		if (csum_len + frag_desc->ip_len > frag_desc->len - offset)
			return -EINVAL;

		frag_desc->trans_proto = protocol;
		csum = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
					csum_len,
					frag_desc->trans_proto, 0);
	} else {
		/* Not checksumable */
		return -EINVAL;
	}

	/* Protocol check */
	if (frag_desc->trans_proto != IPPROTO_TCP &&
	    frag_desc->trans_proto != IPPROTO_UDP)
		return -EINVAL;

	offset += frag_desc->ip_len;
	/* Check for UDP zero csum packets */
	if (frag_desc->trans_proto == IPPROTO_UDP) {
		struct udphdr *uh, __uh;

		uh = rmnet_frag_header_ptr(frag_desc, offset, sizeof(*uh),
					   &__uh);
		if (!uh)
			return -EINVAL;

		if (!uh->check) {
			if (frag_desc->ip_proto == 4) {
				/* Zero checksum is valid */
				priv->stats.csum_sw++;
				return 1;
			}

			/* Not valid in IPv6 */
			priv->stats.csum_sw++;
			return 0;
		}
	}

	/* Walk the frags and checksum each chunk */
	list_for_each_entry(frag, &frag_desc->frags, list) {
		u32 frag_size = skb_frag_size(&frag->frag);

		if (!csum_len)
			break;

		if (offset < frag_size) {
			void *addr = skb_frag_address(&frag->frag) + offset;
			u32 len = min_t(u32, csum_len, frag_size - offset);

			/* Checksum 'len' bytes and add them in */
			csum = csum_partial(addr, len, csum);
			csum_len -= len;
			offset = 0;
		} else {
			offset -= frag_size;
		}
	}

	priv->stats.csum_sw++;
	return !csum_fold(csum);
}

/* Process a QMAPv5 packet header */
int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc,
				       struct rmnet_port *port,
@@ -1440,8 +1589,19 @@ int rmnet_frag_process_next_hdr_packet(struct rmnet_frag_descriptor *frag_desc,
			priv->stats.csum_ok++;
			frag_desc->csum_valid = true;
		} else {
			int valid = rmnet_frag_checksum_pkt(frag_desc);

			if (valid < 0) {
				priv->stats.csum_validation_failed++;
			} else if (valid) {
				/* All's good */
				priv->stats.csum_ok++;
				frag_desc->csum_valid = true;
			} else {
				/* Checksum is actually bad */
				priv->stats.csum_valid_unset++;
			}
		}

		if (!rmnet_frag_pull(frag_desc, port,
				     offset + sizeof(*csum_hdr))) {
+3 −1
Original line number Diff line number Diff line
/* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
 * ​​​​Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.​
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -75,7 +76,8 @@ int rmnet_frag_descriptor_add_frags_from(struct rmnet_frag_descriptor *to,
					 struct rmnet_frag_descriptor *from,
					 u32 off, u32 len);
int rmnet_frag_ipv6_skip_exthdr(struct rmnet_frag_descriptor *frag_desc,
				int start, u8 *nexthdrp, __be16 *fragp);
				int start, u8 *nexthdrp, __be16 *frag_offp,
				bool *frag_hdrp);

/* QMAP command packets */
void rmnet_frag_command(struct rmnet_frag_descriptor *frag_desc,