xref: /linux/drivers/net/wireless/intel/iwlwifi/iwl-utils.c (revision 8be4d31cb8aaeea27bde4b7ddb26e28a89062ebf)
1 /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
2 /*
3  * Copyright (C) 2024-2025 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
iwl_tx_tso_segment(struct sk_buff * skb,unsigned int num_subframes,netdev_features_t netdev_flags,struct sk_buff_head * mpdus_skbs)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 
iwl_div_by_db(u32 value,u8 db)86 static u32 iwl_div_by_db(u32 value, u8 db)
87 {
88 	/*
89 	 * 2^32 * 10**(i / 10) for i = [1, 10], skipping 0 and simply stopping
90 	 * at 10 dB and looping instead of using a much larger table.
91 	 *
92 	 * Using 64 bit math is overkill, but means the helper does not require
93 	 * a limit on the input range.
94 	 */
95 	static const u32 db_to_val[] = {
96 		0xcb59185e, 0xa1866ba8, 0x804dce7a, 0x65ea59fe, 0x50f44d89,
97 		0x404de61f, 0x331426af, 0x2892c18b, 0x203a7e5b, 0x1999999a,
98 	};
99 
100 	while (value && db > 0) {
101 		u8 change = min_t(u8, db, ARRAY_SIZE(db_to_val));
102 
103 		value = (((u64)value) * db_to_val[change - 1]) >> 32;
104 
105 		db -= change;
106 	}
107 
108 	return value;
109 }
110 
iwl_average_neg_dbm(const u8 * neg_dbm_values,u8 len)111 s8 iwl_average_neg_dbm(const u8 *neg_dbm_values, u8 len)
112 {
113 	int average_magnitude;
114 	u32 average_factor;
115 	int sum_magnitude = -128;
116 	u32 sum_factor = 0;
117 	int i, count = 0;
118 
119 	/*
120 	 * To properly average the decibel values (signal values given in dBm)
121 	 * we need to do the math in linear space.  Doing a linear average of
122 	 * dB (dBm) values is a bit annoying though due to the large range of
123 	 * at least -10 to -110 dBm that will not fit into a 32 bit integer.
124 	 *
125 	 * A 64 bit integer should be sufficient, but then we still have the
126 	 * problem that there are no directly usable utility functions
127 	 * available.
128 	 *
129 	 * So, lets not deal with that and instead do much of the calculation
130 	 * with a 16.16 fixed point integer along with a base in dBm. 16.16 bit
131 	 * gives us plenty of head-room for adding up a few values and even
132 	 * doing some math on it. And the tail should be accurate enough too
133 	 * (1/2^16 is somewhere around -48 dB, so effectively zero).
134 	 *
135 	 * i.e. the real value of sum is:
136 	 *      sum = sum_factor / 2^16 * 10^(sum_magnitude / 10) mW
137 	 *
138 	 * However, that does mean we need to be able to bring two values to
139 	 * a common base, so we need a helper for that.
140 	 *
141 	 * Note that this function takes an input with unsigned negative dBm
142 	 * values but returns a signed dBm (i.e. a negative value).
143 	 */
144 
145 	for (i = 0; i < len; i++) {
146 		int val_magnitude;
147 		u32 val_factor;
148 
149 		/* Assume invalid */
150 		if (neg_dbm_values[i] == 0xff)
151 			continue;
152 
153 		val_factor = 0x10000;
154 		val_magnitude = -neg_dbm_values[i];
155 
156 		if (val_magnitude <= sum_magnitude) {
157 			u8 div_db = sum_magnitude - val_magnitude;
158 
159 			val_factor = iwl_div_by_db(val_factor, div_db);
160 			val_magnitude = sum_magnitude;
161 		} else {
162 			u8 div_db = val_magnitude - sum_magnitude;
163 
164 			sum_factor = iwl_div_by_db(sum_factor, div_db);
165 			sum_magnitude = val_magnitude;
166 		}
167 
168 		sum_factor += val_factor;
169 		count++;
170 	}
171 
172 	/* No valid noise measurement, return a very high noise level */
173 	if (count == 0)
174 		return 0;
175 
176 	average_magnitude = sum_magnitude;
177 	average_factor = sum_factor / count;
178 
179 	/*
180 	 * average_factor will be a number smaller than 1.0 (0x10000) at this
181 	 * point. What we need to do now is to adjust average_magnitude so that
182 	 * average_factor is between -0.5 dB and 0.5 dB.
183 	 *
184 	 * Just do -1 dB steps and find the point where
185 	 *   -0.5 dB * -i dB = 0x10000 * 10^(-0.5/10) / i dB
186 	 *                   = div_by_db(0xe429, i)
187 	 * is smaller than average_factor.
188 	 */
189 	for (i = 0; average_factor < iwl_div_by_db(0xe429, i); i++) {
190 		/* nothing */
191 	}
192 
193 	return clamp(average_magnitude - i, -128, 0);
194 }
195 IWL_EXPORT_SYMBOL(iwl_average_neg_dbm);
196