diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/iwl-utils.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/iwl-utils.c | 195 | 
1 files changed, 195 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/iwl-utils.c b/sys/contrib/dev/iwlwifi/iwl-utils.c new file mode 100644 index 000000000000..d503544fda40 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/iwl-utils.c @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2024-2025 Intel Corporation + */ +#include <net/gso.h> +#include <linux/ieee80211.h> +#include <net/ip.h> + +#include "iwl-drv.h" +#include "iwl-utils.h" + +#ifdef CONFIG_INET +int iwl_tx_tso_segment(struct sk_buff *skb, unsigned int num_subframes, +		       netdev_features_t netdev_flags, +		       struct sk_buff_head *mpdus_skbs) +{ +	struct sk_buff *tmp, *next; +	struct ieee80211_hdr *hdr = (void *)skb->data; +	char cb[sizeof(skb->cb)]; +	u16 i = 0; +	unsigned int tcp_payload_len; +	unsigned int mss = skb_shinfo(skb)->gso_size; +	bool ipv4 = (skb->protocol == htons(ETH_P_IP)); +	bool qos = ieee80211_is_data_qos(hdr->frame_control); +	u16 ip_base_id = ipv4 ? ntohs(ip_hdr(skb)->id) : 0; + +	skb_shinfo(skb)->gso_size = num_subframes * mss; +	memcpy(cb, skb->cb, sizeof(cb)); + +	next = skb_gso_segment(skb, netdev_flags); +	skb_shinfo(skb)->gso_size = mss; +	skb_shinfo(skb)->gso_type = ipv4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6; + +	if (IS_ERR(next) && PTR_ERR(next) == -ENOMEM) +		return -ENOMEM; + +	if (WARN_ONCE(IS_ERR(next), +		      "skb_gso_segment error: %d\n", (int)PTR_ERR(next))) +		return PTR_ERR(next); + +	if (next) +		consume_skb(skb); + +	skb_list_walk_safe(next, tmp, next) { +		memcpy(tmp->cb, cb, sizeof(tmp->cb)); +		/* +		 * Compute the length of all the data added for the A-MSDU. +		 * This will be used to compute the length to write in the TX +		 * command. We have: SNAP + IP + TCP for n -1 subframes and +		 * ETH header for n subframes. +		 */ +		tcp_payload_len = skb_tail_pointer(tmp) - +			skb_transport_header(tmp) - +			tcp_hdrlen(tmp) + tmp->data_len; + +		if (ipv4) +			ip_hdr(tmp)->id = htons(ip_base_id + i * num_subframes); + +		if (tcp_payload_len > mss) { +			skb_shinfo(tmp)->gso_size = mss; +			skb_shinfo(tmp)->gso_type = ipv4 ? SKB_GSO_TCPV4 : +							   SKB_GSO_TCPV6; +		} else { +			if (qos) { +				u8 *qc; + +				if (ipv4) +					ip_send_check(ip_hdr(tmp)); + +				qc = ieee80211_get_qos_ctl((void *)tmp->data); +				*qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; +			} +			skb_shinfo(tmp)->gso_size = 0; +		} + +		skb_mark_not_on_list(tmp); +		__skb_queue_tail(mpdus_skbs, tmp); +		i++; +	} + +	return 0; +} +IWL_EXPORT_SYMBOL(iwl_tx_tso_segment); +#endif /* CONFIG_INET */ + +static u32 iwl_div_by_db(u32 value, u8 db) +{ +	/* +	 * 2^32 * 10**(i / 10) for i = [1, 10], skipping 0 and simply stopping +	 * at 10 dB and looping instead of using a much larger table. +	 * +	 * Using 64 bit math is overkill, but means the helper does not require +	 * a limit on the input range. +	 */ +	static const u32 db_to_val[] = { +		0xcb59185e, 0xa1866ba8, 0x804dce7a, 0x65ea59fe, 0x50f44d89, +		0x404de61f, 0x331426af, 0x2892c18b, 0x203a7e5b, 0x1999999a, +	}; + +	while (value && db > 0) { +		u8 change = min_t(u8, db, ARRAY_SIZE(db_to_val)); + +		value = (((u64)value) * db_to_val[change - 1]) >> 32; + +		db -= change; +	} + +	return value; +} + +s8 iwl_average_neg_dbm(const u8 *neg_dbm_values, u8 len) +{ +	int average_magnitude; +	u32 average_factor; +	int sum_magnitude = -128; +	u32 sum_factor = 0; +	int i, count = 0; + +	/* +	 * To properly average the decibel values (signal values given in dBm) +	 * we need to do the math in linear space.  Doing a linear average of +	 * dB (dBm) values is a bit annoying though due to the large range of +	 * at least -10 to -110 dBm that will not fit into a 32 bit integer. +	 * +	 * A 64 bit integer should be sufficient, but then we still have the +	 * problem that there are no directly usable utility functions +	 * available. +	 * +	 * So, lets not deal with that and instead do much of the calculation +	 * with a 16.16 fixed point integer along with a base in dBm. 16.16 bit +	 * gives us plenty of head-room for adding up a few values and even +	 * doing some math on it. And the tail should be accurate enough too +	 * (1/2^16 is somewhere around -48 dB, so effectively zero). +	 * +	 * i.e. the real value of sum is: +	 *      sum = sum_factor / 2^16 * 10^(sum_magnitude / 10) mW +	 * +	 * However, that does mean we need to be able to bring two values to +	 * a common base, so we need a helper for that. +	 * +	 * Note that this function takes an input with unsigned negative dBm +	 * values but returns a signed dBm (i.e. a negative value). +	 */ + +	for (i = 0; i < len; i++) { +		int val_magnitude; +		u32 val_factor; + +		/* Assume invalid */ +		if (neg_dbm_values[i] == 0xff) +			continue; + +		val_factor = 0x10000; +		val_magnitude = -neg_dbm_values[i]; + +		if (val_magnitude <= sum_magnitude) { +			u8 div_db = sum_magnitude - val_magnitude; + +			val_factor = iwl_div_by_db(val_factor, div_db); +			val_magnitude = sum_magnitude; +		} else { +			u8 div_db = val_magnitude - sum_magnitude; + +			sum_factor = iwl_div_by_db(sum_factor, div_db); +			sum_magnitude = val_magnitude; +		} + +		sum_factor += val_factor; +		count++; +	} + +	/* No valid noise measurement, return a very high noise level */ +	if (count == 0) +		return 0; + +	average_magnitude = sum_magnitude; +	average_factor = sum_factor / count; + +	/* +	 * average_factor will be a number smaller than 1.0 (0x10000) at this +	 * point. What we need to do now is to adjust average_magnitude so that +	 * average_factor is between -0.5 dB and 0.5 dB. +	 * +	 * Just do -1 dB steps and find the point where +	 *   -0.5 dB * -i dB = 0x10000 * 10^(-0.5/10) / i dB +	 *                   = div_by_db(0xe429, i) +	 * is smaller than average_factor. +	 */ +	for (i = 0; average_factor < iwl_div_by_db(0xe429, i); i++) { +		/* nothing */ +	} + +	return clamp(average_magnitude - i, -128, 0); +} +IWL_EXPORT_SYMBOL(iwl_average_neg_dbm); | 
