162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/export.h>
362306a36Sopenharmony_ci#include <linux/if_vlan.h>
462306a36Sopenharmony_ci#include <net/ip.h>
562306a36Sopenharmony_ci#include <net/tso.h>
662306a36Sopenharmony_ci#include <asm/unaligned.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_civoid tso_build_hdr(const struct sk_buff *skb, char *hdr, struct tso_t *tso,
962306a36Sopenharmony_ci		   int size, bool is_last)
1062306a36Sopenharmony_ci{
1162306a36Sopenharmony_ci	int hdr_len = skb_transport_offset(skb) + tso->tlen;
1262306a36Sopenharmony_ci	int mac_hdr_len = skb_network_offset(skb);
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci	memcpy(hdr, skb->data, hdr_len);
1562306a36Sopenharmony_ci	if (!tso->ipv6) {
1662306a36Sopenharmony_ci		struct iphdr *iph = (void *)(hdr + mac_hdr_len);
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci		iph->id = htons(tso->ip_id);
1962306a36Sopenharmony_ci		iph->tot_len = htons(size + hdr_len - mac_hdr_len);
2062306a36Sopenharmony_ci		tso->ip_id++;
2162306a36Sopenharmony_ci	} else {
2262306a36Sopenharmony_ci		struct ipv6hdr *iph = (void *)(hdr + mac_hdr_len);
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci		iph->payload_len = htons(size + tso->tlen);
2562306a36Sopenharmony_ci	}
2662306a36Sopenharmony_ci	hdr += skb_transport_offset(skb);
2762306a36Sopenharmony_ci	if (tso->tlen != sizeof(struct udphdr)) {
2862306a36Sopenharmony_ci		struct tcphdr *tcph = (struct tcphdr *)hdr;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci		put_unaligned_be32(tso->tcp_seq, &tcph->seq);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci		if (!is_last) {
3362306a36Sopenharmony_ci			/* Clear all special flags for not last packet */
3462306a36Sopenharmony_ci			tcph->psh = 0;
3562306a36Sopenharmony_ci			tcph->fin = 0;
3662306a36Sopenharmony_ci			tcph->rst = 0;
3762306a36Sopenharmony_ci		}
3862306a36Sopenharmony_ci	} else {
3962306a36Sopenharmony_ci		struct udphdr *uh = (struct udphdr *)hdr;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci		uh->len = htons(sizeof(*uh) + size);
4262306a36Sopenharmony_ci	}
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ciEXPORT_SYMBOL(tso_build_hdr);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_civoid tso_build_data(const struct sk_buff *skb, struct tso_t *tso, int size)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	tso->tcp_seq += size; /* not worth avoiding this operation for UDP */
4962306a36Sopenharmony_ci	tso->size -= size;
5062306a36Sopenharmony_ci	tso->data += size;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if ((tso->size == 0) &&
5362306a36Sopenharmony_ci	    (tso->next_frag_idx < skb_shinfo(skb)->nr_frags)) {
5462306a36Sopenharmony_ci		skb_frag_t *frag = &skb_shinfo(skb)->frags[tso->next_frag_idx];
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci		/* Move to next segment */
5762306a36Sopenharmony_ci		tso->size = skb_frag_size(frag);
5862306a36Sopenharmony_ci		tso->data = skb_frag_address(frag);
5962306a36Sopenharmony_ci		tso->next_frag_idx++;
6062306a36Sopenharmony_ci	}
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ciEXPORT_SYMBOL(tso_build_data);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ciint tso_start(struct sk_buff *skb, struct tso_t *tso)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	int tlen = skb_is_gso_tcp(skb) ? tcp_hdrlen(skb) : sizeof(struct udphdr);
6762306a36Sopenharmony_ci	int hdr_len = skb_transport_offset(skb) + tlen;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	tso->tlen = tlen;
7062306a36Sopenharmony_ci	tso->ip_id = ntohs(ip_hdr(skb)->id);
7162306a36Sopenharmony_ci	tso->tcp_seq = (tlen != sizeof(struct udphdr)) ? ntohl(tcp_hdr(skb)->seq) : 0;
7262306a36Sopenharmony_ci	tso->next_frag_idx = 0;
7362306a36Sopenharmony_ci	tso->ipv6 = vlan_get_protocol(skb) == htons(ETH_P_IPV6);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	/* Build first data */
7662306a36Sopenharmony_ci	tso->size = skb_headlen(skb) - hdr_len;
7762306a36Sopenharmony_ci	tso->data = skb->data + hdr_len;
7862306a36Sopenharmony_ci	if ((tso->size == 0) &&
7962306a36Sopenharmony_ci	    (tso->next_frag_idx < skb_shinfo(skb)->nr_frags)) {
8062306a36Sopenharmony_ci		skb_frag_t *frag = &skb_shinfo(skb)->frags[tso->next_frag_idx];
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		/* Move to next segment */
8362306a36Sopenharmony_ci		tso->size = skb_frag_size(frag);
8462306a36Sopenharmony_ci		tso->data = skb_frag_address(frag);
8562306a36Sopenharmony_ci		tso->next_frag_idx++;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci	return hdr_len;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ciEXPORT_SYMBOL(tso_start);
90