int dhd_tcpack_suppress_set(dhd_pub_t *dhdp, uint8 mode)
{
	int ret = BCME_OK;

	dhd_os_tcpacklock(dhdp);

	if (dhdp->tcpack_sup_mode == mode) {
		DHD_ERROR(("%s %d: already set to %d\n", __FUNCTION__, __LINE__, mode));
		goto exit;
	}

	if (mode >= TCPACK_SUP_LAST_MODE ||
#ifndef BCMSDIO
		mode == TCPACK_SUP_DELAYTX ||
#endif
		FALSE) {
		DHD_ERROR(("%s %d: Invalid mode %d\n", __FUNCTION__, __LINE__, mode));
		ret = BCME_BADARG;
		goto exit;
	}

	DHD_TRACE(("%s: %d -> %d\n",
		__FUNCTION__, dhdp->tcpack_sup_mode, mode));

	/* Old tcpack_sup_mode is TCPACK_SUP_DELAYTX */
	if (dhdp->tcpack_sup_mode == TCPACK_SUP_DELAYTX) {
		tcpack_sup_module_t *tcpack_sup_mod = dhdp->tcpack_sup_module;
		/* We won't need tdata_psh_info pool and tcpddata_info_tbl anymore */
		_tdata_psh_info_pool_deinit(dhdp, tcpack_sup_mod);
		tcpack_sup_mod->tcpdata_info_cnt = 0;
		bzero(tcpack_sup_mod->tcpdata_info_tbl,
			sizeof(tcpdata_info_t) * TCPDATA_INFO_MAXNUM);
		/* For half duplex bus interface, tx precedes rx by default */
		if (dhdp->bus)
			dhd_bus_set_dotxinrx(dhdp->bus, TRUE);
	}

	dhdp->tcpack_sup_mode = mode;

	if (mode == TCPACK_SUP_OFF) {
		ASSERT(dhdp->tcpack_sup_module != NULL);
		MFREE(dhdp->osh, dhdp->tcpack_sup_module, sizeof(tcpack_sup_module_t));
		dhdp->tcpack_sup_module = NULL;
		goto exit;
	}

	if (dhdp->tcpack_sup_module == NULL) {
		tcpack_sup_module_t *tcpack_sup_mod =
			MALLOC(dhdp->osh, sizeof(tcpack_sup_module_t));
		if (tcpack_sup_mod == NULL) {
			DHD_ERROR(("%s %d: No MEM\n", __FUNCTION__, __LINE__));
			dhdp->tcpack_sup_mode = TCPACK_SUP_OFF;
			ret = BCME_NOMEM;
			goto exit;
		}
		bzero(tcpack_sup_mod, sizeof(tcpack_sup_module_t));
		dhdp->tcpack_sup_module = tcpack_sup_mod;
	}

	if (mode == TCPACK_SUP_DELAYTX) {
		ret = _tdata_psh_info_pool_init(dhdp, dhdp->tcpack_sup_module);
		if (ret != BCME_OK)
			DHD_ERROR(("%s %d: pool init fail with %d\n", __FUNCTION__, __LINE__, ret));
		else if (dhdp->bus)
			dhd_bus_set_dotxinrx(dhdp->bus, FALSE);
	}

exit:
	dhd_os_tcpackunlock(dhdp);
	return ret;
}
bool
dhd_tcpack_suppress(dhd_pub_t *dhdp, void *pkt)
{
	uint8 *new_ether_hdr;	/* Ethernet header of the new packet */
	uint16 new_ether_type;	/* Ethernet type of the new packet */
	uint8 *new_ip_hdr;		/* IP header of the new packet */
	uint8 *new_tcp_hdr;		/* TCP header of the new packet */
	uint32 new_ip_hdr_len;	/* IP header length of the new packet */
	uint32 cur_framelen;
	uint32 new_tcp_ack_num;		/* TCP acknowledge number of the new packet */
	uint16 new_ip_total_len;	/* Total length of IP packet for the new packet */
	uint32 new_tcp_hdr_len;		/* TCP header length of the new packet */
	tcpack_sup_module_t *tcpack_sup_mod;
	tcpack_info_t *tcpack_info_tbl;
	int i;
	bool ret = FALSE;
	bool set_dotxinrx = TRUE;

	if (dhdp->tcpack_sup_mode == TCPACK_SUP_OFF)
		goto exit;

	new_ether_hdr = PKTDATA(dhdp->osh, pkt);
	cur_framelen = PKTLEN(dhdp->osh, pkt);

	if (cur_framelen < TCPACKSZMIN || cur_framelen > TCPACKSZMAX) {
		DHD_TRACE(("%s %d: Too short or long length %d to be TCP ACK\n",
			__FUNCTION__, __LINE__, cur_framelen));
		goto exit;
	}

	new_ether_type = new_ether_hdr[12] << 8 | new_ether_hdr[13];

	if (new_ether_type != ETHER_TYPE_IP) {
		DHD_TRACE(("%s %d: Not a IP packet 0x%x\n",
			__FUNCTION__, __LINE__, new_ether_type));
		goto exit;
	}

	DHD_TRACE(("%s %d: IP pkt! 0x%x\n", __FUNCTION__, __LINE__, new_ether_type));

	new_ip_hdr = new_ether_hdr + ETHER_HDR_LEN;
	cur_framelen -= ETHER_HDR_LEN;

	ASSERT(cur_framelen >= IPV4_MIN_HEADER_LEN);

	new_ip_hdr_len = IPV4_HLEN(new_ip_hdr);
	if (IP_VER(new_ip_hdr) != IP_VER_4 || IPV4_PROT(new_ip_hdr) != IP_PROT_TCP) {
		DHD_TRACE(("%s %d: Not IPv4 nor TCP! ip ver %d, prot %d\n",
			__FUNCTION__, __LINE__, IP_VER(new_ip_hdr), IPV4_PROT(new_ip_hdr)));
		goto exit;
	}

	new_tcp_hdr = new_ip_hdr + new_ip_hdr_len;
	cur_framelen -= new_ip_hdr_len;

	ASSERT(cur_framelen >= TCP_MIN_HEADER_LEN);

	DHD_TRACE(("%s %d: TCP pkt!\n", __FUNCTION__, __LINE__));

	/* is it an ack ? Allow only ACK flag, not to suppress others. */
	if (new_tcp_hdr[TCP_FLAGS_OFFSET] != TCP_FLAG_ACK) {
		DHD_TRACE(("%s %d: Do not touch TCP flag 0x%x\n",
			__FUNCTION__, __LINE__, new_tcp_hdr[TCP_FLAGS_OFFSET]));
		goto exit;
	}

	new_ip_total_len = ntoh16_ua(&new_ip_hdr[IPV4_PKTLEN_OFFSET]);
	new_tcp_hdr_len = 4 * TCP_HDRLEN(new_tcp_hdr[TCP_HLEN_OFFSET]);

	/* This packet has TCP data, so just send */
	if (new_ip_total_len > new_ip_hdr_len + new_tcp_hdr_len) {
		DHD_TRACE(("%s %d: Do nothing for TCP DATA\n", __FUNCTION__, __LINE__));
		goto exit;
	}

	ASSERT(new_ip_total_len == new_ip_hdr_len + new_tcp_hdr_len);

	new_tcp_ack_num = ntoh32_ua(&new_tcp_hdr[TCP_ACK_NUM_OFFSET]);

	DHD_TRACE(("%s %d: TCP ACK with zero DATA length"
		" IP addr "IPV4_ADDR_STR" "IPV4_ADDR_STR" TCP port %d %d\n",
		__FUNCTION__, __LINE__,
		IPV4_ADDR_TO_STR(ntoh32_ua(&new_ip_hdr[IPV4_SRC_IP_OFFSET])),
		IPV4_ADDR_TO_STR(ntoh32_ua(&new_ip_hdr[IPV4_DEST_IP_OFFSET])),
		ntoh16_ua(&new_tcp_hdr[TCP_SRC_PORT_OFFSET]),
		ntoh16_ua(&new_tcp_hdr[TCP_DEST_PORT_OFFSET])));

	/* Look for tcp_ack_info that has the same ip src/dst addrs and tcp src/dst ports */
	dhd_os_tcpacklock(dhdp);
#if defined(DEBUG_COUNTER) && defined(DHDTCPACK_SUP_DBG)
	counter_printlog(&tack_tbl);
	tack_tbl.cnt[0]++;
#endif /* DEBUG_COUNTER && DHDTCPACK_SUP_DBG */

	tcpack_sup_mod = dhdp->tcpack_sup_module;
	tcpack_info_tbl = tcpack_sup_mod->tcpack_info_tbl;

	if (!tcpack_sup_mod) {
		DHD_ERROR(("%s %d: tcpack suppress module NULL!!\n", __FUNCTION__, __LINE__));
		ret = BCME_ERROR;
		dhd_os_tcpackunlock(dhdp);
		goto exit;
	}

	if (dhd_tcpdata_psh_acked(dhdp, new_ip_hdr, new_tcp_hdr, new_tcp_ack_num)) {
		/* This TCPACK is ACK to TCPDATA PSH pkt, so keep set_dotxinrx TRUE */
#if defined(DEBUG_COUNTER) && defined(DHDTCPACK_SUP_DBG)
		tack_tbl.cnt[5]++;
#endif /* DEBUG_COUNTER && DHDTCPACK_SUP_DBG */
	} else
		set_dotxinrx = FALSE;

	for (i = 0; i < tcpack_sup_mod->tcpack_info_cnt; i++) {
		void *oldpkt;	/* TCPACK packet that is already in txq or DelayQ */
		uint8 *old_ether_hdr, *old_ip_hdr, *old_tcp_hdr;
		uint32 old_ip_hdr_len, old_tcp_hdr_len;
		uint32 old_tcpack_num;	/* TCP ACK number of old TCPACK packet in Q */

		if ((oldpkt = tcpack_info_tbl[i].pkt_in_q) == NULL) {
			DHD_ERROR(("%s %d: Unexpected error!! cur idx %d, ttl cnt %d\n",
				__FUNCTION__, __LINE__, i, tcpack_sup_mod->tcpack_info_cnt));
			break;
		}

		if (PKTDATA(dhdp->osh, oldpkt) == NULL) {
			DHD_ERROR(("%s %d: oldpkt data NULL!! cur idx %d, ttl cnt %d\n",
				__FUNCTION__, __LINE__, i, tcpack_sup_mod->tcpack_info_cnt));
			break;
		}

		old_ether_hdr = tcpack_info_tbl[i].pkt_ether_hdr;
		old_ip_hdr = old_ether_hdr + ETHER_HDR_LEN;
		old_ip_hdr_len = IPV4_HLEN(old_ip_hdr);
		old_tcp_hdr = old_ip_hdr + old_ip_hdr_len;
		old_tcp_hdr_len = 4 * TCP_HDRLEN(old_tcp_hdr[TCP_HLEN_OFFSET]);

		DHD_TRACE(("%s %d: oldpkt %p[%d], IP addr "IPV4_ADDR_STR" "IPV4_ADDR_STR
			" TCP port %d %d\n", __FUNCTION__, __LINE__, oldpkt, i,
			IPV4_ADDR_TO_STR(ntoh32_ua(&old_ip_hdr[IPV4_SRC_IP_OFFSET])),
			IPV4_ADDR_TO_STR(ntoh32_ua(&old_ip_hdr[IPV4_DEST_IP_OFFSET])),
			ntoh16_ua(&old_tcp_hdr[TCP_SRC_PORT_OFFSET]),
			ntoh16_ua(&old_tcp_hdr[TCP_DEST_PORT_OFFSET])));

		/* If either of IP address or TCP port number does not match, skip. */
		if (memcmp(&new_ip_hdr[IPV4_SRC_IP_OFFSET],
			&old_ip_hdr[IPV4_SRC_IP_OFFSET], IPV4_ADDR_LEN * 2) ||
			memcmp(&new_tcp_hdr[TCP_SRC_PORT_OFFSET],
			&old_tcp_hdr[TCP_SRC_PORT_OFFSET], TCP_PORT_LEN * 2))
			continue;

		old_tcpack_num = ntoh32_ua(&old_tcp_hdr[TCP_ACK_NUM_OFFSET]);

		if (IS_TCPSEQ_GT(new_tcp_ack_num, old_tcpack_num)) {
			/* New packet has higher TCP ACK number, so it replaces the old packet */
			if (new_ip_hdr_len == old_ip_hdr_len &&
				new_tcp_hdr_len == old_tcp_hdr_len) {
				ASSERT(memcmp(new_ether_hdr, old_ether_hdr, ETHER_HDR_LEN) == 0);
				bcopy(new_ip_hdr, old_ip_hdr, new_ip_total_len);
				PKTFREE(dhdp->osh, pkt, FALSE);
				DHD_TRACE(("%s %d: TCP ACK replace %u -> %u\n",
					__FUNCTION__, __LINE__, old_tcpack_num, new_tcp_ack_num));
#if defined(DEBUG_COUNTER) && defined(DHDTCPACK_SUP_DBG)
				tack_tbl.cnt[2]++;
#endif /* DEBUG_COUNTER && DHDTCPACK_SUP_DBG */
				ret = TRUE;
			} else {
#if defined(DEBUG_COUNTER) && defined(DHDTCPACK_SUP_DBG)
				tack_tbl.cnt[6]++;
#endif /* DEBUG_COUNTER && DHDTCPACK_SUP_DBG */
				DHD_TRACE(("%s %d: lenth mismatch %d != %d || %d != %d"
					" ACK %u -> %u\n", __FUNCTION__, __LINE__,
					new_ip_hdr_len, old_ip_hdr_len,
					new_tcp_hdr_len, old_tcp_hdr_len,
					old_tcpack_num, new_tcp_ack_num));
			}
		} else if (new_tcp_ack_num == old_tcpack_num) {
			set_dotxinrx = TRUE;
			/* TCPACK retransmission */
#if defined(DEBUG_COUNTER) && defined(DHDTCPACK_SUP_DBG)
			tack_tbl.cnt[3]++;
#endif /* DEBUG_COUNTER && DHDTCPACK_SUP_DBG */
		} else {
			DHD_TRACE(("%s %d: ACK number reverse old %u(0x%p) new %u(0x%p)\n",
				__FUNCTION__, __LINE__, old_tcpack_num, oldpkt,
				new_tcp_ack_num, pkt));
		}
		dhd_os_tcpackunlock(dhdp);
		goto exit;
	}

	if (i == tcpack_sup_mod->tcpack_info_cnt && i < TCPACK_INFO_MAXNUM) {
		/* No TCPACK packet with the same IP addr and TCP port is found
		 * in tcp_ack_info_tbl. So add this packet to the table.
		 */
		DHD_TRACE(("%s %d: Add pkt 0x%p(ether_hdr 0x%p) to tbl[%d]\n",
			__FUNCTION__, __LINE__, pkt, new_ether_hdr,
			tcpack_sup_mod->tcpack_info_cnt));

		tcpack_info_tbl[tcpack_sup_mod->tcpack_info_cnt].pkt_in_q = pkt;
		tcpack_info_tbl[tcpack_sup_mod->tcpack_info_cnt].pkt_ether_hdr = new_ether_hdr;
		tcpack_sup_mod->tcpack_info_cnt++;
#if defined(DEBUG_COUNTER) && defined(DHDTCPACK_SUP_DBG)
		tack_tbl.cnt[1]++;
#endif /* DEBUG_COUNTER && DHDTCPACK_SUP_DBG */
	} else {
		ASSERT(i == tcpack_sup_mod->tcpack_info_cnt);
		DHD_TRACE(("%s %d: No empty tcp ack info tbl\n",
			__FUNCTION__, __LINE__));
	}
	dhd_os_tcpackunlock(dhdp);

exit:
	/* Unless TCPACK_SUP_DELAYTX, dotxinrx is alwasy TRUE, so no need to set here */
	if (dhdp->tcpack_sup_mode == TCPACK_SUP_DELAYTX && set_dotxinrx)
		dhd_bus_set_dotxinrx(dhdp->bus, TRUE);

	return ret;
}
int dhd_tcpack_suppress_set(dhd_pub_t *dhdp, uint8 mode)
{
	int ret = BCME_OK;
	unsigned long flags;

	flags = dhd_os_tcpacklock(dhdp);

	if (dhdp->tcpack_sup_mode == mode) {
		DHD_ERROR(("%s %d: already set to %d\n", __FUNCTION__, __LINE__, mode));
		goto exit;
	}

	if (mode >= TCPACK_SUP_LAST_MODE ||
#ifndef BCMSDIO
		mode == TCPACK_SUP_DELAYTX ||
#endif /* !BCMSDIO */
		FALSE) {
		DHD_ERROR(("%s %d: Invalid mode %d\n", __FUNCTION__, __LINE__, mode));
		ret = BCME_BADARG;
		goto exit;
	}

	DHD_TRACE(("%s: %d -> %d\n",
		__FUNCTION__, dhdp->tcpack_sup_mode, mode));

#ifdef BCMSDIO
	/* Old tcpack_sup_mode is TCPACK_SUP_DELAYTX */
	if (dhdp->tcpack_sup_mode == TCPACK_SUP_DELAYTX) {
		tcpack_sup_module_t *tcpack_sup_mod = dhdp->tcpack_sup_module;
		/* We won't need tdata_psh_info pool and tcpddata_info_tbl anymore */
		_tdata_psh_info_pool_deinit(dhdp, tcpack_sup_mod);
		tcpack_sup_mod->tcpdata_info_cnt = 0;
		bzero(tcpack_sup_mod->tcpdata_info_tbl,
			sizeof(tcpdata_info_t) * TCPDATA_INFO_MAXNUM);
		/* For half duplex bus interface, tx precedes rx by default */
		if (dhdp->bus)
			dhd_bus_set_dotxinrx(dhdp->bus, TRUE);
	}
#endif
	dhdp->tcpack_sup_mode = mode;

	if (mode == TCPACK_SUP_OFF) {
		ASSERT(dhdp->tcpack_sup_module != NULL);
		/* Clean up timer/data structure for any remaining/pending packet or timer. */
		dhd_tcpack_info_tbl_clean(dhdp);
		MFREE(dhdp->osh, dhdp->tcpack_sup_module, sizeof(tcpack_sup_module_t));
		dhdp->tcpack_sup_module = NULL;
		goto exit;
	}

	if (dhdp->tcpack_sup_module == NULL) {
		tcpack_sup_module_t *tcpack_sup_mod =
			MALLOC(dhdp->osh, sizeof(tcpack_sup_module_t));
		if (tcpack_sup_mod == NULL) {
			DHD_ERROR(("%s %d: No MEM\n", __FUNCTION__, __LINE__));
			dhdp->tcpack_sup_mode = TCPACK_SUP_OFF;
			ret = BCME_NOMEM;
			goto exit;
		}
		bzero(tcpack_sup_mod, sizeof(tcpack_sup_module_t));
		dhdp->tcpack_sup_module = tcpack_sup_mod;
	}

#ifdef BCMSDIO
	if (mode == TCPACK_SUP_DELAYTX) {
		ret = _tdata_psh_info_pool_init(dhdp, dhdp->tcpack_sup_module);
		if (ret != BCME_OK)
			DHD_ERROR(("%s %d: pool init fail with %d\n", __FUNCTION__, __LINE__, ret));
		else if (dhdp->bus)
			dhd_bus_set_dotxinrx(dhdp->bus, FALSE);
	}
#endif /* BCMSDIO */

	if (mode == TCPACK_SUP_HOLD) {
		int i;
		tcpack_sup_module_t *tcpack_sup_mod =
			(tcpack_sup_module_t *)dhdp->tcpack_sup_module;
		dhdp->tcpack_sup_ratio = TCPACK_SUPP_RATIO;
		dhdp->tcpack_sup_delay = TCPACK_DELAY_TIME;
		for (i = 0; i < TCPACK_INFO_MAXNUM; i++)
		{
			tcpack_sup_mod->tcpack_info_tbl[i].dhdp = dhdp;
			init_timer(&tcpack_sup_mod->tcpack_info_tbl[i].timer);
			tcpack_sup_mod->tcpack_info_tbl[i].timer.data =
				(ulong)&tcpack_sup_mod->tcpack_info_tbl[i];
			tcpack_sup_mod->tcpack_info_tbl[i].timer.function = dhd_tcpack_send;
		}
	}

exit:
	dhd_os_tcpackunlock(dhdp, flags);
	return ret;
}