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