/*
 * Copyright (c) 2012-2017 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

/*=== header file includes ===*/
/* generic utilities */
#include <qdf_nbuf.h>           /* qdf_nbuf_t, etc. */
#include <qdf_timer.h>
#include <qdf_time.h>

/* datapath internal interfaces */
#include <ol_txrx_internal.h>   /* TXRX_ASSERT, etc. */
#include <ol_rx_reorder.h>      /* ol_rx_reorder_flush, etc. */

#ifdef QCA_SUPPORT_OL_RX_REORDER_TIMEOUT

void ol_rx_reorder_timeout_remove(struct ol_txrx_peer_t *peer, unsigned int tid)
{
	struct ol_txrx_pdev_t *pdev;
	struct ol_tx_reorder_cat_timeout_t *rx_reorder_timeout_ac;
	struct ol_rx_reorder_timeout_list_elem_t *list_elem;
	int ac;

	pdev = peer->vdev->pdev;
	ac = TXRX_TID_TO_WMM_AC(tid);
	rx_reorder_timeout_ac = &pdev->rx.reorder_timeout.access_cats[ac];
	list_elem = &peer->tids_rx_reorder[tid].timeout;
	if (!list_elem->active) {
		/* this element has already been removed */
		return;
	}
	list_elem->active = 0;
	TAILQ_REMOVE(&rx_reorder_timeout_ac->virtual_timer_list, list_elem,
		     reorder_timeout_list_elem);
}

static void
ol_rx_reorder_timeout_start(struct ol_tx_reorder_cat_timeout_t
			    *rx_reorder_timeout_ac, uint32_t time_now_ms)
{
	uint32_t duration_ms;
	struct ol_rx_reorder_timeout_list_elem_t *list_elem;

	list_elem = TAILQ_FIRST(&rx_reorder_timeout_ac->virtual_timer_list);

	duration_ms = list_elem->timestamp_ms - time_now_ms;
	qdf_timer_start(&rx_reorder_timeout_ac->timer, duration_ms);
}

static inline void
ol_rx_reorder_timeout_add(struct ol_txrx_peer_t *peer, uint8_t tid)
{
	uint32_t time_now_ms;
	struct ol_txrx_pdev_t *pdev;
	struct ol_tx_reorder_cat_timeout_t *rx_reorder_timeout_ac;
	struct ol_rx_reorder_timeout_list_elem_t *list_elem;
	int ac;
	int start;

	pdev = peer->vdev->pdev;
	ac = TXRX_TID_TO_WMM_AC(tid);
	rx_reorder_timeout_ac = &pdev->rx.reorder_timeout.access_cats[ac];
	list_elem = &peer->tids_rx_reorder[tid].timeout;

	list_elem->active = 1;
	list_elem->peer = peer;
	list_elem->tid = tid;

	/* set the expiration timestamp */
	time_now_ms = qdf_system_ticks_to_msecs(qdf_system_ticks());
	list_elem->timestamp_ms =
		time_now_ms + rx_reorder_timeout_ac->duration_ms;

	/* add to the queue */
	start = TAILQ_EMPTY(&rx_reorder_timeout_ac->virtual_timer_list);
	TAILQ_INSERT_TAIL(&rx_reorder_timeout_ac->virtual_timer_list,
			  list_elem, reorder_timeout_list_elem);
	if (start)
		ol_rx_reorder_timeout_start(rx_reorder_timeout_ac, time_now_ms);
}

void ol_rx_reorder_timeout_update(struct ol_txrx_peer_t *peer, uint8_t tid)
{
	if (!peer)
		return;

	/*
	 * If there are no holes, i.e. no queued frames,
	 * then timeout doesn't apply.
	 */
	if (peer->tids_rx_reorder[tid].num_mpdus == 0)
		return;

	/*
	 * If the virtual timer for this peer-TID is already running,
	 * then leave it.
	 */
	if (peer->tids_rx_reorder[tid].timeout.active)
		return;

	ol_rx_reorder_timeout_add(peer, tid);
}

static void ol_rx_reorder_timeout(void *arg)
{
	struct ol_txrx_pdev_t *pdev;
	struct ol_rx_reorder_timeout_list_elem_t *list_elem, *tmp;
	uint32_t time_now_ms;
	struct ol_tx_reorder_cat_timeout_t *rx_reorder_timeout_ac;

	rx_reorder_timeout_ac = (struct ol_tx_reorder_cat_timeout_t *)arg;
	time_now_ms = qdf_system_ticks_to_msecs(qdf_system_ticks());

	pdev = rx_reorder_timeout_ac->pdev;
	qdf_spin_lock(&pdev->rx.mutex);
/* TODO: conditionally take mutex lock during regular rx */
	TAILQ_FOREACH_SAFE(list_elem,
			   &rx_reorder_timeout_ac->virtual_timer_list,
			   reorder_timeout_list_elem, tmp) {
		unsigned int idx_start, idx_end;
		struct ol_txrx_peer_t *peer;

		if (list_elem->timestamp_ms > time_now_ms)
			break;  /* time has not expired yet for this element */

		list_elem->active = 0;
		/* remove the expired element from the list */
		TAILQ_REMOVE(&rx_reorder_timeout_ac->virtual_timer_list,
			     list_elem, reorder_timeout_list_elem);

		peer = list_elem->peer;

		idx_start = 0xffff;     /* start from next_rel_idx */
		ol_rx_reorder_first_hole(peer, list_elem->tid, &idx_end);
		ol_rx_reorder_flush(peer->vdev,
				    peer,
				    list_elem->tid,
				    idx_start, idx_end, htt_rx_flush_release);
	}
	/* restart the timer if unexpired elements are left in the list */
	if (!TAILQ_EMPTY(&rx_reorder_timeout_ac->virtual_timer_list))
		ol_rx_reorder_timeout_start(rx_reorder_timeout_ac, time_now_ms);

	qdf_spin_unlock(&pdev->rx.mutex);
}

void ol_rx_reorder_timeout_init(struct ol_txrx_pdev_t *pdev)
{
	int i;

	for (i = 0; i < QDF_ARRAY_SIZE(pdev->rx.reorder_timeout.access_cats);
		i++) {
		struct ol_tx_reorder_cat_timeout_t *rx_reorder_timeout_ac;

		rx_reorder_timeout_ac =
			&pdev->rx.reorder_timeout.access_cats[i];
		/* init the per-AC timers */
		qdf_timer_init(pdev->osdev,
				       &rx_reorder_timeout_ac->timer,
				       ol_rx_reorder_timeout,
				       rx_reorder_timeout_ac,
				       QDF_TIMER_TYPE_SW);
		/* init the virtual timer list */
		TAILQ_INIT(&rx_reorder_timeout_ac->virtual_timer_list);
		rx_reorder_timeout_ac->pdev = pdev;
	}
	pdev->rx.reorder_timeout.access_cats[TXRX_WMM_AC_VO].duration_ms = 40;
	pdev->rx.reorder_timeout.access_cats[TXRX_WMM_AC_VI].duration_ms = 100;
	pdev->rx.reorder_timeout.access_cats[TXRX_WMM_AC_BE].duration_ms = 100;
	pdev->rx.reorder_timeout.access_cats[TXRX_WMM_AC_BK].duration_ms = 100;
}

void ol_rx_reorder_timeout_peer_cleanup(struct ol_txrx_peer_t *peer)
{
	int tid;

	for (tid = 0; tid < OL_TXRX_NUM_EXT_TIDS; tid++) {
		if (peer->tids_rx_reorder[tid].timeout.active)
			ol_rx_reorder_timeout_remove(peer, tid);
	}
}

void ol_rx_reorder_timeout_cleanup(struct ol_txrx_pdev_t *pdev)
{
	int i;

	for (i = 0; i < QDF_ARRAY_SIZE(pdev->rx.reorder_timeout.access_cats);
		i++) {
		struct ol_tx_reorder_cat_timeout_t *rx_reorder_timeout_ac;

		rx_reorder_timeout_ac =
			&pdev->rx.reorder_timeout.access_cats[i];
		qdf_timer_stop(&rx_reorder_timeout_ac->timer);
		qdf_timer_free(&rx_reorder_timeout_ac->timer);
	}
}

#endif /* QCA_SUPPORT_OL_RX_REORDER_TIMEOUT */
