18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (c) 2007   The University of Aberdeen, Scotland, UK
48c2ecf20Sopenharmony_ci *  Copyright (c) 2005-7 The University of Waikato, Hamilton, New Zealand.
58c2ecf20Sopenharmony_ci *  Copyright (c) 2005-7 Ian McDonald <ian.mcdonald@jandi.co.nz>
68c2ecf20Sopenharmony_ci *  Copyright (c) 2005 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci#include <net/sock.h>
98c2ecf20Sopenharmony_ci#include "tfrc.h"
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_cistatic struct kmem_cache  *tfrc_lh_slab  __read_mostly;
128c2ecf20Sopenharmony_ci/* Loss Interval weights from [RFC 3448, 5.4], scaled by 10 */
138c2ecf20Sopenharmony_cistatic const int tfrc_lh_weights[NINTERVAL] = { 10, 10, 10, 10, 8, 6, 4, 2 };
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/* implements LIFO semantics on the array */
168c2ecf20Sopenharmony_cistatic inline u8 LIH_INDEX(const u8 ctr)
178c2ecf20Sopenharmony_ci{
188c2ecf20Sopenharmony_ci	return LIH_SIZE - 1 - (ctr % LIH_SIZE);
198c2ecf20Sopenharmony_ci}
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/* the `counter' index always points at the next entry to be populated */
228c2ecf20Sopenharmony_cistatic inline struct tfrc_loss_interval *tfrc_lh_peek(struct tfrc_loss_hist *lh)
238c2ecf20Sopenharmony_ci{
248c2ecf20Sopenharmony_ci	return lh->counter ? lh->ring[LIH_INDEX(lh->counter - 1)] : NULL;
258c2ecf20Sopenharmony_ci}
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/* given i with 0 <= i <= k, return I_i as per the rfc3448bis notation */
288c2ecf20Sopenharmony_cistatic inline u32 tfrc_lh_get_interval(struct tfrc_loss_hist *lh, const u8 i)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	BUG_ON(i >= lh->counter);
318c2ecf20Sopenharmony_ci	return lh->ring[LIH_INDEX(lh->counter - i - 1)]->li_length;
328c2ecf20Sopenharmony_ci}
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/*
358c2ecf20Sopenharmony_ci *	On-demand allocation and de-allocation of entries
368c2ecf20Sopenharmony_ci */
378c2ecf20Sopenharmony_cistatic struct tfrc_loss_interval *tfrc_lh_demand_next(struct tfrc_loss_hist *lh)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	if (lh->ring[LIH_INDEX(lh->counter)] == NULL)
408c2ecf20Sopenharmony_ci		lh->ring[LIH_INDEX(lh->counter)] = kmem_cache_alloc(tfrc_lh_slab,
418c2ecf20Sopenharmony_ci								    GFP_ATOMIC);
428c2ecf20Sopenharmony_ci	return lh->ring[LIH_INDEX(lh->counter)];
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_civoid tfrc_lh_cleanup(struct tfrc_loss_hist *lh)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	if (!tfrc_lh_is_initialised(lh))
488c2ecf20Sopenharmony_ci		return;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	for (lh->counter = 0; lh->counter < LIH_SIZE; lh->counter++)
518c2ecf20Sopenharmony_ci		if (lh->ring[LIH_INDEX(lh->counter)] != NULL) {
528c2ecf20Sopenharmony_ci			kmem_cache_free(tfrc_lh_slab,
538c2ecf20Sopenharmony_ci					lh->ring[LIH_INDEX(lh->counter)]);
548c2ecf20Sopenharmony_ci			lh->ring[LIH_INDEX(lh->counter)] = NULL;
558c2ecf20Sopenharmony_ci		}
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic void tfrc_lh_calc_i_mean(struct tfrc_loss_hist *lh)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	u32 i_i, i_tot0 = 0, i_tot1 = 0, w_tot = 0;
618c2ecf20Sopenharmony_ci	int i, k = tfrc_lh_length(lh) - 1; /* k is as in rfc3448bis, 5.4 */
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	if (k <= 0)
648c2ecf20Sopenharmony_ci		return;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	for (i = 0; i <= k; i++) {
678c2ecf20Sopenharmony_ci		i_i = tfrc_lh_get_interval(lh, i);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci		if (i < k) {
708c2ecf20Sopenharmony_ci			i_tot0 += i_i * tfrc_lh_weights[i];
718c2ecf20Sopenharmony_ci			w_tot  += tfrc_lh_weights[i];
728c2ecf20Sopenharmony_ci		}
738c2ecf20Sopenharmony_ci		if (i > 0)
748c2ecf20Sopenharmony_ci			i_tot1 += i_i * tfrc_lh_weights[i-1];
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	lh->i_mean = max(i_tot0, i_tot1) / w_tot;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci/**
818c2ecf20Sopenharmony_ci * tfrc_lh_update_i_mean  -  Update the `open' loss interval I_0
828c2ecf20Sopenharmony_ci * For recomputing p: returns `true' if p > p_prev  <=>  1/p < 1/p_prev
838c2ecf20Sopenharmony_ci */
848c2ecf20Sopenharmony_ciu8 tfrc_lh_update_i_mean(struct tfrc_loss_hist *lh, struct sk_buff *skb)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	struct tfrc_loss_interval *cur = tfrc_lh_peek(lh);
878c2ecf20Sopenharmony_ci	u32 old_i_mean = lh->i_mean;
888c2ecf20Sopenharmony_ci	s64 len;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	if (cur == NULL)			/* not initialised */
918c2ecf20Sopenharmony_ci		return 0;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	len = dccp_delta_seqno(cur->li_seqno, DCCP_SKB_CB(skb)->dccpd_seq) + 1;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	if (len - (s64)cur->li_length <= 0)	/* duplicate or reordered */
968c2ecf20Sopenharmony_ci		return 0;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (SUB16(dccp_hdr(skb)->dccph_ccval, cur->li_ccval) > 4)
998c2ecf20Sopenharmony_ci		/*
1008c2ecf20Sopenharmony_ci		 * Implements RFC 4342, 10.2:
1018c2ecf20Sopenharmony_ci		 * If a packet S (skb) exists whose seqno comes `after' the one
1028c2ecf20Sopenharmony_ci		 * starting the current loss interval (cur) and if the modulo-16
1038c2ecf20Sopenharmony_ci		 * distance from C(cur) to C(S) is greater than 4, consider all
1048c2ecf20Sopenharmony_ci		 * subsequent packets as belonging to a new loss interval. This
1058c2ecf20Sopenharmony_ci		 * test is necessary since CCVal may wrap between intervals.
1068c2ecf20Sopenharmony_ci		 */
1078c2ecf20Sopenharmony_ci		cur->li_is_closed = 1;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (tfrc_lh_length(lh) == 1)		/* due to RFC 3448, 6.3.1 */
1108c2ecf20Sopenharmony_ci		return 0;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	cur->li_length = len;
1138c2ecf20Sopenharmony_ci	tfrc_lh_calc_i_mean(lh);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	return lh->i_mean < old_i_mean;
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/* Determine if `new_loss' does begin a new loss interval [RFC 4342, 10.2] */
1198c2ecf20Sopenharmony_cistatic inline u8 tfrc_lh_is_new_loss(struct tfrc_loss_interval *cur,
1208c2ecf20Sopenharmony_ci				     struct tfrc_rx_hist_entry *new_loss)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	return	dccp_delta_seqno(cur->li_seqno, new_loss->tfrchrx_seqno) > 0 &&
1238c2ecf20Sopenharmony_ci		(cur->li_is_closed || SUB16(new_loss->tfrchrx_ccval, cur->li_ccval) > 4);
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci/**
1278c2ecf20Sopenharmony_ci * tfrc_lh_interval_add  -  Insert new record into the Loss Interval database
1288c2ecf20Sopenharmony_ci * @lh:		   Loss Interval database
1298c2ecf20Sopenharmony_ci * @rh:		   Receive history containing a fresh loss event
1308c2ecf20Sopenharmony_ci * @calc_first_li: Caller-dependent routine to compute length of first interval
1318c2ecf20Sopenharmony_ci * @sk:		   Used by @calc_first_li in caller-specific way (subtyping)
1328c2ecf20Sopenharmony_ci *
1338c2ecf20Sopenharmony_ci * Updates I_mean and returns 1 if a new interval has in fact been added to @lh.
1348c2ecf20Sopenharmony_ci */
1358c2ecf20Sopenharmony_ciint tfrc_lh_interval_add(struct tfrc_loss_hist *lh, struct tfrc_rx_hist *rh,
1368c2ecf20Sopenharmony_ci			 u32 (*calc_first_li)(struct sock *), struct sock *sk)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	struct tfrc_loss_interval *cur = tfrc_lh_peek(lh), *new;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	if (cur != NULL && !tfrc_lh_is_new_loss(cur, tfrc_rx_hist_loss_prev(rh)))
1418c2ecf20Sopenharmony_ci		return 0;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	new = tfrc_lh_demand_next(lh);
1448c2ecf20Sopenharmony_ci	if (unlikely(new == NULL)) {
1458c2ecf20Sopenharmony_ci		DCCP_CRIT("Cannot allocate/add loss record.");
1468c2ecf20Sopenharmony_ci		return 0;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	new->li_seqno	  = tfrc_rx_hist_loss_prev(rh)->tfrchrx_seqno;
1508c2ecf20Sopenharmony_ci	new->li_ccval	  = tfrc_rx_hist_loss_prev(rh)->tfrchrx_ccval;
1518c2ecf20Sopenharmony_ci	new->li_is_closed = 0;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	if (++lh->counter == 1)
1548c2ecf20Sopenharmony_ci		lh->i_mean = new->li_length = (*calc_first_li)(sk);
1558c2ecf20Sopenharmony_ci	else {
1568c2ecf20Sopenharmony_ci		cur->li_length = dccp_delta_seqno(cur->li_seqno, new->li_seqno);
1578c2ecf20Sopenharmony_ci		new->li_length = dccp_delta_seqno(new->li_seqno,
1588c2ecf20Sopenharmony_ci				  tfrc_rx_hist_last_rcv(rh)->tfrchrx_seqno) + 1;
1598c2ecf20Sopenharmony_ci		if (lh->counter > (2*LIH_SIZE))
1608c2ecf20Sopenharmony_ci			lh->counter -= LIH_SIZE;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		tfrc_lh_calc_i_mean(lh);
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci	return 1;
1658c2ecf20Sopenharmony_ci}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ciint __init tfrc_li_init(void)
1688c2ecf20Sopenharmony_ci{
1698c2ecf20Sopenharmony_ci	tfrc_lh_slab = kmem_cache_create("tfrc_li_hist",
1708c2ecf20Sopenharmony_ci					 sizeof(struct tfrc_loss_interval), 0,
1718c2ecf20Sopenharmony_ci					 SLAB_HWCACHE_ALIGN, NULL);
1728c2ecf20Sopenharmony_ci	return tfrc_lh_slab == NULL ? -ENOBUFS : 0;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_civoid tfrc_li_exit(void)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	if (tfrc_lh_slab != NULL) {
1788c2ecf20Sopenharmony_ci		kmem_cache_destroy(tfrc_lh_slab);
1798c2ecf20Sopenharmony_ci		tfrc_lh_slab = NULL;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci}
182