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