xref: /linux/drivers/net/wireless/intel/iwlwifi/iwl-utils.c (revision 7fffcb5cceea5cec643da76671607c6cc5c8e8be)
1 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
2 /*
3  * Copyright (C) 2024 Intel Corporation
4  */
5 #include <net/gso.h>
6 #include <linux/ieee80211.h>
7 #include <net/ip.h>
8 
9 #include "iwl-drv.h"
10 #include "iwl-utils.h"
11 
12 #ifdef CONFIG_INET
13 int iwl_tx_tso_segment(struct sk_buff *skb, unsigned int num_subframes,
14 		       netdev_features_t netdev_flags,
15 		       struct sk_buff_head *mpdus_skbs)
16 {
17 	struct sk_buff *tmp, *next;
18 	struct ieee80211_hdr *hdr = (void *)skb->data;
19 	char cb[sizeof(skb->cb)];
20 	u16 i = 0;
21 	unsigned int tcp_payload_len;
22 	unsigned int mss = skb_shinfo(skb)->gso_size;
23 	bool ipv4 = (skb->protocol == htons(ETH_P_IP));
24 	bool qos = ieee80211_is_data_qos(hdr->frame_control);
25 	u16 ip_base_id = ipv4 ? ntohs(ip_hdr(skb)->id) : 0;
26 
27 	skb_shinfo(skb)->gso_size = num_subframes * mss;
28 	memcpy(cb, skb->cb, sizeof(cb));
29 
30 	next = skb_gso_segment(skb, netdev_flags);
31 	skb_shinfo(skb)->gso_size = mss;
32 	skb_shinfo(skb)->gso_type = ipv4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
33 
34 	if (IS_ERR(next) && PTR_ERR(next) == -ENOMEM)
35 		return -ENOMEM;
36 
37 	if (WARN_ONCE(IS_ERR(next),
38 		      "skb_gso_segment error: %d\n", (int)PTR_ERR(next)))
39 		return PTR_ERR(next);
40 
41 	if (next)
42 		consume_skb(skb);
43 
44 	skb_list_walk_safe(next, tmp, next) {
45 		memcpy(tmp->cb, cb, sizeof(tmp->cb));
46 		/*
47 		 * Compute the length of all the data added for the A-MSDU.
48 		 * This will be used to compute the length to write in the TX
49 		 * command. We have: SNAP + IP + TCP for n -1 subframes and
50 		 * ETH header for n subframes.
51 		 */
52 		tcp_payload_len = skb_tail_pointer(tmp) -
53 			skb_transport_header(tmp) -
54 			tcp_hdrlen(tmp) + tmp->data_len;
55 
56 		if (ipv4)
57 			ip_hdr(tmp)->id = htons(ip_base_id + i * num_subframes);
58 
59 		if (tcp_payload_len > mss) {
60 			skb_shinfo(tmp)->gso_size = mss;
61 			skb_shinfo(tmp)->gso_type = ipv4 ? SKB_GSO_TCPV4 :
62 							   SKB_GSO_TCPV6;
63 		} else {
64 			if (qos) {
65 				u8 *qc;
66 
67 				if (ipv4)
68 					ip_send_check(ip_hdr(tmp));
69 
70 				qc = ieee80211_get_qos_ctl((void *)tmp->data);
71 				*qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT;
72 			}
73 			skb_shinfo(tmp)->gso_size = 0;
74 		}
75 
76 		skb_mark_not_on_list(tmp);
77 		__skb_queue_tail(mpdus_skbs, tmp);
78 		i++;
79 	}
80 
81 	return 0;
82 }
83 IWL_EXPORT_SYMBOL(iwl_tx_tso_segment);
84 #endif /* CONFIG_INET */
85