// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2019 MediaTek Inc.
 */
#include <linux/kthread.h>
#include "ccci_bm.h"
#include "port_proxy.h"
#include "port_udc.h"
#include "port_smem.h"

#define MAX_QUEUE_LENGTH 16

#define Min(a, b) (a < b ? a : b)
#define MAX_PACKET_SIZE 2555872 /* 2.4375*1024*1024 -32 */

atomic_t udc_status = ATOMIC_INIT(0);

struct ap_md_rw_index *rw_index;
unsigned char *comp_data_buf_base, *uncomp_data_buf_base;
unsigned char *uncomp_cache_data_base;
struct udc_comp_req_t *req_des_0_base, *req_des_1_base;
struct udc_comp_rslt_t *rslt_des_0_base, *rslt_des_1_base;
static unsigned int total_comp_size;

void set_udc_status(struct sk_buff *skb)
{
	struct ccci_udc_cmd_rsp_t *udc_cmd_rsp =
		(struct ccci_udc_cmd_rsp_t *)skb->data;

	if (udc_cmd_rsp->udc_inst_id == 0 &&
		udc_cmd_rsp->udc_cmd == UDC_CMD_KICK)
		atomic_set(&udc_status, UDC_HighKick);
	if (udc_cmd_rsp->udc_cmd == UDC_CMD_DEACTV)
		atomic_set(&udc_status, UDC_DEACTV);
	if (udc_cmd_rsp->udc_cmd == UDC_CMD_DISC)
		atomic_set(&udc_status, UDC_DISCARD);
}

int udc_resp_msg_to_md(struct port_t *port,
	struct sk_buff *skb, int handle_udc_ret)
{
	int data_len, ret;
	struct ccci_udc_cmd_rsp_t *udc_cmd_rsp =
		(struct ccci_udc_cmd_rsp_t *)skb->data;

	/* write back to modem */
	/* update message */
	udc_cmd_rsp->udc_cmd |= UDC_API_RESP_ID;
	data_len = sizeof(*udc_cmd_rsp);
	if (handle_udc_ret < 0) {
		udc_cmd_rsp->rslt = UDC_CMD_RSLT_ERROR;
		CCCI_NORMAL_LOG(0, UDC,
			"rsp ins%d cmd:0x%x,rslt:%d\n",
			udc_cmd_rsp->udc_inst_id,
			udc_cmd_rsp->udc_cmd, udc_cmd_rsp->rslt);
	} else
		udc_cmd_rsp->rslt = UDC_CMD_RSLT_OK;

	/* resize skb */
	CCCI_DEBUG_LOG(0, UDC,
		"data_len:%d,skb->len:%d\n", data_len, skb->len);
	if (data_len > skb->len)
		skb_put(skb, data_len - skb->len);
	else if (data_len < skb->len)
		skb_trim(skb, data_len);
	/* update CCCI header */
	udc_cmd_rsp->header.channel = CCCI_UDC_TX;
	udc_cmd_rsp->header.data[1] = data_len;
	CCCI_DEBUG_LOG(0, UDC,
		"Write %d/%d, %08X, %08X, %08X, %08X, op_id=0x%x\n",
		skb->len, data_len, udc_cmd_rsp->header.data[0],
		udc_cmd_rsp->header.data[1], udc_cmd_rsp->header.channel,
		udc_cmd_rsp->header.reserved, udc_cmd_rsp->udc_cmd);
	/* switch to Tx request */
	ret = port_send_skb_to_md(port, skb, 1);

	CCCI_DEBUG_LOG(0, UDC,
		"send_skb_to_md:%d,rsp ins%d cmd:0x%x,rslt:%d\n",
		ret, udc_cmd_rsp->udc_inst_id,
		udc_cmd_rsp->udc_cmd, udc_cmd_rsp->rslt);

	return ret;
}

void udc_cmd_check(struct port_t *port,
	struct sk_buff **skb_tmp, struct sk_buff **skb,
	u32 inst_id, struct udc_state_ctl *ctl)
{
	unsigned long flags;
	struct ccci_udc_deactv_param_t *ccci_udc_deactv;
	struct ccci_udc_actv_param_t *ccci_udc_actv;
	int skb_len, skb_tmp_len, skb_tmp1_len;
	struct sk_buff *skb_tmp1 = NULL;
	u32 ins_id_tmp;

	ctl->last_state = ctl->curr_state;
	ctl->curr_state = atomic_read(&udc_status);

	switch (ctl->curr_state) {
	case UDC_DEACTV_DONE:
	case UDC_DISC_DONE:
		atomic_set(&udc_status, UDC_IDLE);
		ctl->curr_state = UDC_IDLE;
		break;
	case UDC_IDLE:
	case UDC_HandleHighKick:
	case UDC_KICKDEACTV:
		break;
	case UDC_HighKick:
	{
		if (inst_id == 0) {
			atomic_set(&udc_status, UDC_IDLE);
			ctl->curr_state = UDC_IDLE;
			break;
		}
		CCCI_DEBUG_LOG(0, UDC,
			"high prio kick0 come in\n");
		if (!skb_queue_empty(&port->rx_skb_list)) {
			skb_len = (*skb)->len;
			skb_tmp_len = (*skb_tmp)->len;
			 /* resize skb */
			 CCCI_DEBUG_LOG(0, UDC,
				"skb_len:%d,skb_tmp_len:%d\n",
				skb_len, skb_tmp_len);
			if (skb_len > skb_tmp_len)
				skb_put(*skb_tmp, skb_len - skb_tmp_len);
			else if (skb_len < skb_tmp_len)
				skb_trim(*skb_tmp, skb_len);

			/* backup skb to skb_tmp */
			skb_copy_to_linear_data(*skb_tmp,
				(*skb)->data, skb_len);
			spin_lock_irqsave(&port->rx_skb_list.lock, flags);
			*skb = __skb_dequeue(&port->rx_skb_list);
			spin_unlock_irqrestore(&port->rx_skb_list.lock, flags);
			ctl->last_state = ctl->curr_state;
			atomic_set(&udc_status, UDC_HandleHighKick);
			ctl->curr_state = UDC_HandleHighKick;
		} else
			goto err;
		break;
	}
	case UDC_DEACTV:
	{
		if (!skb_queue_empty(&port->rx_skb_list)) {
			if (ctl->last_state == UDC_HandleHighKick) {
				skb_tmp1 = ccci_alloc_skb(
					sizeof(*ccci_udc_actv), 1, 1);
				if (unlikely(!skb_tmp1)) {
					CCCI_ERROR_LOG(0, UDC,
						"alloc skb_tmp1 fail\n");
					return;
				}
				/* backup skb_tmp to skb_tmp1 */
				skb_copy_to_linear_data(skb_tmp1,
					(*skb_tmp)->data, (*skb_tmp)->len);
				skb_put(skb_tmp1, (*skb_tmp)->len);
			}
			skb_len = (*skb)->len;
			skb_tmp_len = (*skb_tmp)->len;
			/* resize skb */
			CCCI_DEBUG_LOG(0, UDC,
				"skb_len:%d,skb_tmp_len:%d\n",
				skb_len, skb_tmp_len);
			if (skb_len > skb_tmp_len)
				skb_put(*skb_tmp, skb_len - skb_tmp_len);
			else if (skb_len < skb_tmp_len)
				skb_trim(*skb_tmp, skb_len);

			/* backup skb to skb_tmp */
			skb_copy_to_linear_data(*skb_tmp,
				(*skb)->data, skb_len);
			spin_lock_irqsave(&port->rx_skb_list.lock, flags);
			/* dequeue */
			*skb = __skb_dequeue(&port->rx_skb_list);
			if ((*skb) == NULL) {
				CCCI_ERROR_LOG(0, UDC,
					"%s:__skb_dequeue fail\n", __func__);
				spin_unlock_irqrestore(&port->rx_skb_list.lock, flags);
				return;
			}
			spin_unlock_irqrestore(&port->rx_skb_list.lock, flags);
			ccci_udc_deactv
				= (struct ccci_udc_deactv_param_t *)
				(*skb)->data;
			ins_id_tmp = ccci_udc_deactv->udc_inst_id;
			if (ctl->last_state == UDC_HandleHighKick) {
				if (ins_id_tmp == 0) {
					skb_tmp_len = (*skb_tmp)->len;
					skb_tmp1_len = skb_tmp1->len;
					if (skb_tmp_len > skb_tmp1_len)
						skb_put(*skb_tmp,
						skb_tmp_len - skb_tmp1_len);
					else if (skb_tmp_len < skb_tmp1_len)
						skb_trim(*skb_tmp, skb_tmp_len);

					skb_copy_to_linear_data(*skb_tmp,
						skb_tmp1->data, skb_tmp1_len);
				}
				ccci_free_skb(skb_tmp1);
				ctl->last_state = ctl->curr_state;
				atomic_set(&udc_status, UDC_KICKDEACTV);
				ctl->curr_state = UDC_KICKDEACTV;
			} else {
				if (ins_id_tmp == inst_id) {
					ctl->last_state = ctl->curr_state;
					atomic_set(&udc_status,
						UDC_DEACTV_DONE);
					ctl->curr_state = UDC_DEACTV_DONE;
				} else {
					ctl->last_state = ctl->curr_state;
					atomic_set(&udc_status, UDC_KICKDEACTV);
					ctl->curr_state = UDC_KICKDEACTV;
				}
			}
		} else
			goto err;
		break;
	}
	case UDC_DISCARD:
	{
		if (!skb_queue_empty(&port->rx_skb_list)) {
			skb_len = (*skb)->len;
			skb_tmp_len = (*skb_tmp)->len;
			/* resize skb */
			CCCI_DEBUG_LOG(0, UDC,
				"skb_len:%d,skb_tmp_len:%d\n",
				skb_len, skb_tmp_len);
			if (skb_len > skb_tmp_len)
				skb_put(*skb_tmp, skb_len - skb_tmp_len);
			else if (skb_len < skb_tmp_len)
				skb_trim(*skb_tmp, skb_len);

			/* backup skb to skb_tmp */
			skb_copy_to_linear_data(*skb_tmp,
				(*skb)->data, skb_len);
			spin_lock_irqsave(&port->rx_skb_list.lock, flags);
			/* dequeue */
			*skb = __skb_dequeue(&port->rx_skb_list);
			spin_unlock_irqrestore(&port->rx_skb_list.lock, flags);

			ctl->last_state = ctl->curr_state;
			atomic_set(&udc_status, UDC_DISC_DONE);
			ctl->curr_state = UDC_DISC_DONE;
		} else
			goto err;
		break;
	}
	default:
		CCCI_ERROR_LOG(0, UDC,
			"[Error]Unknown UDC STATUS (0x%08X)\n",
			atomic_read(&udc_status));
		break;
	}
	if (ctl->last_state != ctl->curr_state)
		CCCI_NORMAL_LOG(0, UDC,
			"udc_status:from %d to %d by %s\n",
			ctl->last_state, ctl->curr_state, __func__);
	return;
err:
	CCCI_ERROR_LOG(0, UDC,
		"udc_status%d:skb list is empty\n", ctl->curr_state);
	atomic_set(&udc_status, UDC_IDLE);
	ctl->curr_state = UDC_IDLE;
}

int udc_actv_handler(struct z_stream_s *zcpr, enum udc_dict_opt_e dic_option,
	unsigned int buffer_size, u32 inst_id)
{
	int ret = 0;
	static struct udc_private_data my_param0, my_param1;

	if (inst_id == 0)
		ret = udc_init(zcpr, &my_param0);
	else if (inst_id == 1)
		ret = udc_init(zcpr, &my_param1);

	ret |= deflateInit2_cb(zcpr, Z_DEFAULT_COMPRESSION,
		Z_DEFLATED, buffer_size, 8, Z_FIXED);

	if (ret < 0) {
		CCCI_ERROR_LOG(-1, UDC,
			"ins%d deflateInit2 fail ret:%d\n", inst_id, ret);
		return ret;
	}
	if (dic_option == UDC_DICT_STD_FOR_SIP) {
		ret |= deflateSetDictionary_cb(zcpr,
			get_dictionary_content(dic_option),
			UDC_DICTIONARY_LENGTH);
		if (ret < 0) {
			CCCI_ERROR_LOG(-1, UDC,
				"ins%d deflateSetDictionary fail ret:%d\n",
				inst_id, ret);
		}
	}

	return ret;
}

/* phase out:when ap recevice deflateEnd cmd, call deflateEnd_cb directly */
int udc_deactv_handler(struct z_stream_s *zcpr, u32 inst_id)
{
	int deflate_end_flag = 0;
	struct udc_comp_req_t *req_des;
	struct udc_comp_rslt_t *rslt_des;
	unsigned int ap_read = 0, ap_write = 0, md_write = 0, md_read = 0;
	struct udc_comp_req_t *req_des_base = NULL;
	struct udc_comp_rslt_t *rslt_des_base = NULL;

	if (inst_id == 0) {
		req_des_base = req_des_0_base;
		rslt_des_base = rslt_des_0_base;
		ap_read = rw_index->md_des_ins0.read;
		md_write = rw_index->md_des_ins0.write;
		md_read = rw_index->ap_resp_ins0.read;
	} else if (inst_id == 1) {
		req_des_base = req_des_1_base;
		rslt_des_base = rslt_des_1_base;
		ap_read = rw_index->md_des_ins1.read;
		md_write = rw_index->md_des_ins1.write;
		md_read = rw_index->ap_resp_ins1.read;
	}

	while (ap_read != md_write) {
		if (inst_id == 0) {
			ap_read = rw_index->md_des_ins0.read;
			ap_write = rw_index->ap_resp_ins0.write;
			md_write = rw_index->md_des_ins0.write;
			md_read = rw_index->ap_resp_ins0.read;
		} else if (inst_id == 1) {
			ap_read = rw_index->md_des_ins1.read;
			ap_write = rw_index->ap_resp_ins1.write;
			md_write = rw_index->md_des_ins1.write;
			md_read = rw_index->ap_resp_ins1.read;
		}

		/* req_des table is only 4kb */
		req_des = req_des_base + ap_read;
		/* md_write must be <=511 */
		ap_read = (ap_read + 1) % 512;
		/* dump req_des */
		CCCI_NORMAL_LOG(-1, UDC,
			"deactv req%d:sdu_idx(%d),sit_type(%d),rst(%d),ap_r(%d),md_w(%d),req_des(%p)\n",
			inst_id, req_des->sdu_idx, req_des->sit_type,
			req_des->rst, ap_read, md_write, req_des);
		if (req_des->con == 0) {
			if ((ap_write+1) == md_read) {
				CCCI_ERROR_LOG(-1, UDC,
					"%s:cmp_rslt table is full\n",
					__func__);
				CCCI_ERROR_LOG(-1, UDC,
					"%s:ins%d md r:%d,md w:%d,ap r:%d,ap w:%d\n",
					__func__, inst_id, ap_read,
					md_write, md_read, ap_write);
				break;
			}
			rslt_des = rslt_des_base + ap_write;
			rslt_des->udc = 0;

			ap_write = (ap_write + 1) % 512;
			/* insure sequential execution */
			mb();
			if (inst_id == 0) {
				rw_index->md_des_ins0.read = ap_read;
				rw_index->ap_resp_ins0.write = ap_write;
			} else if (inst_id == 1) {
				rw_index->md_des_ins1.read = ap_read;
				rw_index->ap_resp_ins1.write = ap_write;
			}
			CCCI_NORMAL_LOG(-1, UDC,
				"deactv rslt%d:sdu_idx(%d),sit_type(%d),udc(%d),rst(%d)\n",
				inst_id, rslt_des->sdu_idx, rslt_des->sit_type,
				rslt_des->udc, rslt_des->rst);
		} else {
			if (inst_id == 0)
				rw_index->md_des_ins0.read = ap_read;
			else if (inst_id == 1)
				rw_index->md_des_ins1.read = ap_read;
		}
	}
	deflate_end_flag = deflateEnd_cb(zcpr);
	udc_deinit(zcpr);

	CCCI_NORMAL_LOG(-1, UDC, "deflateEnd_ins%d,ret:%d\n",
		inst_id, deflate_end_flag);
	if (deflate_end_flag < 0) {
		/* the continuous input is unprocessed,
		 *it maybe return -3
		 */
		if (deflate_end_flag == -3)
			return 0;
		else
			return deflate_end_flag;
	}
	return deflate_end_flag;
}

static void ccci_udc_req_data_dump(u32 inst_id,
	unsigned char *uncomp_data, unsigned int uncomp_len)
{
#ifdef UDC_DATA_DUMP
	int j = 0;

	CCCI_HISTORY_TAG_LOG(-1, UDC,
		"req%d:uncomp_data:%p\n", inst_id, uncomp_data);
	for (j = 0; j < 16; j++) {
		if (j % 16 == 0)
			CCCI_HISTORY_LOG(-1, UDC, "%04X:", j);
		CCCI_MEM_LOG(-1, UDC, " %02X", uncomp_data[j]);
		if (j == 15)
			CCCI_HISTORY_LOG(-1, UDC, "\n");
	}
#endif
}

static void ccci_udc_rslt_data_dump(u32 inst_id,
	unsigned char *comp_data, unsigned int comp_len)
{
#ifdef UDC_DATA_DUMP
	unsigned int ap_read, ap_write, md_read, md_write;
	unsigned int j = 0;

	if (inst_id == 0) {
		ap_read = rw_index->md_des_ins0.read;
		ap_write = rw_index->ap_resp_ins0.write;
		md_read = rw_index->ap_resp_ins0.read;
		md_write = rw_index->md_des_ins0.write;
	} else if (inst_id == 1) {
		ap_read = rw_index->md_des_ins1.read;
		ap_write = rw_index->ap_resp_ins1.write;
		md_read = rw_index->ap_resp_ins1.read;
		md_write = rw_index->md_des_ins1.write;
	}

	CCCI_HISTORY_TAG_LOG(-1, UDC,
		"rst%d:comp_len:%d,comp_data:%p\n",
		inst_id, comp_len, comp_data);
	CCCI_HISTORY_TAG_LOG(-1, UDC,
		"ins%d:md r:%d,md w:%d,ap r:%d,ap w:%d\n",
		inst_id, ap_read, md_write, md_read, ap_write);

	for (j = 0; j < comp_len; j++) {
		if (j % 16 == 0) {
			if (j > 0)
				CCCI_HISTORY_LOG(-1, UDC, "\n%04X:", j);
			else
				CCCI_HISTORY_LOG(-1, UDC, "%04X:", j);
		}
		CCCI_HISTORY_LOG(-1, UDC, " %02X", *(comp_data + j));
		if (j == (comp_len - 1))
			CCCI_HISTORY_LOG(-1, UDC, "\n");
	}
#endif
}

/* <0:full 0:not full */
static int check_cmp_buf(u32 inst_id,
	int max_output_size)
{
	unsigned int ap_read = 0, ap_write = 0, md_read = 0, md_read_len = 0;
	struct udc_comp_req_t *req_des, *req_des_base = NULL;
	struct udc_comp_rslt_t *rslt_des, *rslt_des_base = NULL;

	if (inst_id == 0) {
		req_des_base = req_des_0_base;
		rslt_des_base = rslt_des_0_base;
		ap_read = rw_index->md_des_ins0.read;
		ap_write = rw_index->ap_resp_ins0.write;
		md_read = rw_index->ap_resp_ins0.read;
	} else if (inst_id == 1) {
		req_des_base = req_des_1_base;
		rslt_des_base = rslt_des_1_base;
		ap_read = rw_index->md_des_ins1.read;
		ap_write = rw_index->ap_resp_ins1.write;
		md_read = rw_index->ap_resp_ins1.read;
	}
	if (!rslt_des_base) {
		CCCI_ERROR_LOG(-1, UDC, "rslt_des_base  null\n");
		return -1;
	}
	md_read_len = (rslt_des_base + md_read)->cmp_addr
		+ (rslt_des_base + md_read)->cmp_len;
	if (total_comp_size < md_read_len) {
		if ((total_comp_size + max_output_size)
				>= md_read_len) {
			CCCI_NORMAL_LOG(-1, UDC,
				"%s:ins%d cmp_buf full,ap_w:%d,ap_r:%d\n",
				__func__, inst_id, ap_write, md_read);
			CCCI_NORMAL_LOG(-1, UDC,
				"(total_comp_size:%d+max_output_size:%d)>md_read_len:%d\n",
				total_comp_size, max_output_size, md_read_len);
			req_des = req_des_base + ap_read;
			rslt_des = rslt_des_base + ap_write;

			rslt_des->sdu_idx = req_des->sdu_idx;
			rslt_des->sit_type = req_des->sit_type;
			rslt_des->udc = 0;

			/* insure sequential execution */
			mb();
			/* update ap write index */
			ap_write = (ap_write + 1) % 512;
			if (inst_id == 0)
				rw_index->ap_resp_ins0.write = ap_write;
			else if (inst_id == 1)
				rw_index->ap_resp_ins1.write = ap_write;
			return -CMP_BUF_FULL;
		}
	}
	return 0;
}

static int cal_udc_param(struct z_stream_s *zcpr, u32 inst_id,
	int *max_output_size, int *udc_chksum)
{
	struct udc_comp_req_t *req_des_tmp, *req_des_base = NULL;
	unsigned int ap_read = 0, md_write = 0;
	unsigned int uncomp_len_total = 0;
	int j = 0;

	if (inst_id == 0) {
		req_des_base = req_des_0_base;
		ap_read = rw_index->md_des_ins0.read;
		md_write = rw_index->md_des_ins0.write;
	} else if (inst_id == 1) {
		req_des_base = req_des_1_base;
		ap_read = rw_index->md_des_ins1.read;
		md_write = rw_index->md_des_ins1.write;
	}

	if (*max_output_size == 0) {
		req_des_tmp = req_des_base + ap_read;
		if (!req_des_tmp) {
			CCCI_ERROR_LOG(-1, UDC,
				"%s:req_des_base&ap_read is null\n",
				__func__);
			return -1;
		}
		if (req_des_tmp->con == 0)
			*max_output_size = deflateBound_cb(zcpr,
			req_des_tmp->seg_len);
		else if (req_des_tmp->con == 1) {
			for (j = 0; ap_read != md_write; j++) {
				req_des_tmp = req_des_base + (ap_read+j)%512;
				uncomp_len_total += req_des_tmp->seg_len;
				if (req_des_tmp->con == 0)
					break;
			}
			*max_output_size =
				deflateBound_cb(zcpr, uncomp_len_total);
			CCCI_DEBUG_LOG(-1, UDC,
				"req_des%d:deflateBound uncomp_len_total:%d,packet_count:%d\n",
				inst_id, uncomp_len_total, j+1);
			/* packet_count > 2*/
			if (j > 1)
				CCCI_ERROR_LOG(-1, UDC,
					"req_des%d:packet_count:%d,md_r:%d,md_w:%d\n",
					inst_id, j+1, ap_read, md_write);
		}
		/* calc chksum before call deflate */
		*udc_chksum = udc_chksum_cb(zcpr);
		CCCI_DEBUG_LOG(-1, UDC,
			"ins%d:max_output_size:%d udc_chksum:%d\n",
			inst_id, *max_output_size, *udc_chksum);
	}

	return 0;
}

int udc_deflate(struct z_stream_s *zcpr, u32 inst_id, u32 con,
	unsigned char *uncomp_data, unsigned int uncomp_len,
	unsigned char *comp_data, unsigned int remain_len)
{
	unsigned long bytes_processed, prev_bytes_processed;
	int deflate_st;
	int comp_len = 0;

	(*zcpr).next_in = uncomp_data;
	(*zcpr).next_out = comp_data;
	(*zcpr).avail_in = uncomp_len;
	(*zcpr).avail_out = remain_len;

	prev_bytes_processed = (*zcpr).total_in;
	deflate_st = deflate_cb(zcpr,
		con ? Z_NO_FLUSH : Z_SYNC_FLUSH);
	if (deflate_st < 0) {
		CCCI_ERROR_LOG(-1, UDC,
			"ins%d:deflate_st:%d\n", inst_id, deflate_st);
		if (deflate_st == Z_BUF_ERROR)
			CCCI_ERROR_LOG(-1, UDC,
				"ins%d zcpr.avail_in:%d,zcpr.avail_out:%d\n",
				inst_id, (*zcpr).avail_in, (*zcpr).avail_out);
		return deflate_st;
	}
	if ((*zcpr).avail_in > 0)
		CCCI_ERROR_LOG(-1, UDC,
			"%d input bytes not processed!\n",
			(*zcpr).avail_in);
	bytes_processed = (*zcpr).total_in - prev_bytes_processed;
	if (bytes_processed != uncomp_len)
		CCCI_ERROR_LOG(-1, UDC,
			"input %d bytes, only process %lu bytes\n",
			uncomp_len, bytes_processed);
	comp_len = udc_GetCmpLen_cb(zcpr, comp_data, (*zcpr).next_out);
	total_comp_size += (*zcpr).next_out - comp_data;
	CCCI_DEBUG_LOG(-1, UDC,
		"ins%d total_comp_size:%d\n",
		inst_id, total_comp_size);

	return comp_len;
}

int udc_kick_handler(struct port_t *port, struct z_stream_s *zcpr,
	u32 inst_id, unsigned char **comp_data)
{
	int ret = 0;
	static int max_output_size;
	int max_packet_size = MAX_PACKET_SIZE;
	static unsigned int udc_chksum;
	static unsigned int is_rst;
	unsigned int ap_read = 0, ap_write = 0, md_read = 0, md_write = 0;
	struct udc_comp_req_t *req_des, *req_des_base = NULL;
	struct udc_comp_rslt_t *rslt_des, *rslt_des_base = NULL;
	unsigned int uncomp_len, comp_len = 0;
	unsigned int remain_len;
	unsigned char *uncomp_data = NULL;
	/* reserved 8k for reduce memcpy op */
	unsigned int rsvd_len = MAX_PACKET_SIZE - 8*1024;

	if (inst_id == 0) {
		req_des_base = req_des_0_base;
		rslt_des_base = rslt_des_0_base;
		ap_read = rw_index->md_des_ins0.read;
		ap_write = rw_index->ap_resp_ins0.write;
		md_read = rw_index->ap_resp_ins0.read;
		md_write = rw_index->md_des_ins0.write;
	} else if (inst_id == 1) {
		req_des_base = req_des_1_base;
		rslt_des_base = rslt_des_1_base;
		ap_read = rw_index->md_des_ins1.read;
		ap_write = rw_index->ap_resp_ins1.write;
		md_read = rw_index->ap_resp_ins1.read;
		md_write = rw_index->md_des_ins1.write;
	}

	/* check if cmp_rslt table is full */
	if ((ap_write+1) == md_read) {
		CCCI_ERROR_LOG(0, UDC,
			"cmp_rslt table is full\n");
		CCCI_ERROR_LOG(0, UDC,
			"ins%d:md r:%d,md w:%d,ap r:%d,ap w:%d\n",
			inst_id, ap_read, md_write, md_read, ap_write);
		return -CMP_RSLT_FULL;
	}
	/* req_des table is only 4kb */
	req_des = req_des_base + ap_read;
	if (req_des == NULL) {
		CCCI_ERROR_LOG(0, UDC, "invalid req_des");
		return -CMP_INST_ID_ERR;
	}
	/* dump req_des */
	CCCI_NORMAL_LOG(0, UDC,
		"req%d:sdu_idx(%d),buf_type(%d),seg_len(%d),phy_offset(%#x)\n",
		inst_id, req_des->sdu_idx, req_des->buf_type,
		req_des->seg_len, req_des->seg_phy_addr);
	uncomp_len = req_des->seg_len;
	if (req_des->buf_type == 0)
		uncomp_data = (unsigned char *)
			((unsigned long)uncomp_data_buf_base +
			req_des->seg_phy_addr);
	else // if (req_des->buf_type == 1)
		uncomp_data = (unsigned char *)
			((unsigned long)uncomp_cache_data_base +
			req_des->seg_phy_addr);

	ccci_udc_req_data_dump(inst_id, uncomp_data, uncomp_len);

	if (req_des->rst == 1) {
		is_rst = 1;
		CCCI_NORMAL_LOG(0, UDC,
			"kick req%d:rst(%d),sdu_idx(%d),md r(%d)\n",
			inst_id, req_des->rst, req_des->sdu_idx, ap_read);
		deflateReset_cb(zcpr);
	}

	/* check max_output_size&udc_chksum */
	cal_udc_param(zcpr, inst_id, &max_output_size, &udc_chksum);

	/* md_write must be <=511 */
	ap_read = (ap_read + 1) % 512;

	if (inst_id == 0)
		rw_index->md_des_ins0.read = ap_read;
	else if (inst_id == 1)
		rw_index->md_des_ins1.read = ap_read;

	/* deinit comp_data to reduce memcpy */
	if (total_comp_size >= rsvd_len ||
		(total_comp_size + max_output_size) > max_packet_size) {
		CCCI_NORMAL_LOG(0, UDC,
			"ins%d total_cmp_size:%d,deflateBound:%d,rsvd_len:%d\n",
			inst_id, total_comp_size, max_output_size, rsvd_len);
		*comp_data = comp_data_buf_base;
		total_comp_size = 0;
	}
	/* cal md_read_len for check if cmp_buf is full or not */
	ret = check_cmp_buf(inst_id, max_output_size);
	if (ret == -CMP_BUF_FULL)
		return ret;
	remain_len = Min(max_output_size,
		max_packet_size - total_comp_size);

	ret = udc_deflate(zcpr, inst_id, req_des->con,
		uncomp_data, uncomp_len, *comp_data, remain_len);
	if (ret < 0)
		return ret;
	comp_len += ret;

	if (req_des->con == 0) {
		rslt_des = rslt_des_base + ap_write;
		rslt_des->sdu_idx = req_des->sdu_idx;
		rslt_des->sit_type = req_des->sit_type;
		rslt_des->udc = 1;
		rslt_des->rst = is_rst;
		rslt_des->cksm = udc_chksum;
		rslt_des->cmp_addr = *comp_data - comp_data_buf_base;
		rslt_des->cmp_len = comp_len;

		CCCI_NORMAL_LOG(0, UDC,
			"rslt%d:sdu_idx(%d),rst(%d),comp_len(%d),offset(%d),chsm(%d),ap_write(%d)\n",
			inst_id, rslt_des->sdu_idx, rslt_des->rst,
			comp_len, rslt_des->cmp_addr, rslt_des->cksm, ap_write);

		if (comp_len == 0) {
			/* if no check comp_len,ke will happen */
			CCCI_ERROR_LOG(0, UDC,
				"kick%d comp_len = 0\n", inst_id);
			return -CMP_ZERO_LEN;
		}

		comp_len = 0;
		is_rst = 0;
		max_output_size = 0;
		/* update ap write index */
		ap_write = (ap_write + 1) % 512;
		/* insure sequential execution */
		mb();
		if (inst_id == 0)
			rw_index->ap_resp_ins0.write = ap_write;
		else if (inst_id == 1)
			rw_index->ap_resp_ins1.write = ap_write;
		ccci_udc_rslt_data_dump(inst_id,
			*comp_data, rslt_des->cmp_len);
	}
	*comp_data = (*zcpr).next_out;

	return ret;
}

int udc_restore_skb(struct port_t *port,
	struct udc_state_ctl *ctl,
	struct sk_buff **skb_tmp, struct sk_buff **skb)
{
	struct ccci_udc_actv_param_t *ccci_udc_actv;
	int ret = 0;

	ctl->last_state = ctl->curr_state;
	/* ctl->curr_state = atomic_read(&udc_status); */

	switch (ctl->curr_state) {
	case UDC_HandleHighKick:
	case UDC_DISC_DONE:
	case UDC_KICKDEACTV:
	case UDC_DEACTV_DONE:
	{
		*skb = ccci_alloc_skb(sizeof(*ccci_udc_actv), 1, 1);
		if (unlikely(!(*skb))) {
			CCCI_ERROR_LOG(0, UDC,
				"%s:alloc skb fail\n", __func__);
			return ret;
		}

		skb_copy_to_linear_data(*skb,
			(*skb_tmp)->data, (*skb_tmp)->len);
		skb_put(*skb, (*skb_tmp)->len);

		ctl->last_state = ctl->curr_state;
		atomic_set(&udc_status, UDC_IDLE);
		ctl->curr_state = UDC_IDLE;
		ret = 1;
		break;
	}
	case UDC_IDLE:
	case UDC_HighKick:
	case UDC_DISCARD:
	case UDC_DEACTV:
		break;
	default:
		CCCI_ERROR_LOG(0, UDC,
			"[Error]%s:Unknown UDC STATUS (0x%08X)\n",
			__func__, atomic_read(&udc_status));
		break;
	}
	if (ctl->last_state != ctl->curr_state)
		CCCI_NORMAL_LOG(0, UDC,
			"udc_status:from %d to %d by %s\n",
			ctl->last_state, ctl->curr_state, __func__);
	return ret;
}

void udc_cmd_handler(struct port_t *port, struct sk_buff *skb)
{
	struct ccci_smem_region *region;
	int ret = 0;
	unsigned int udc_cmd = 0;
	struct udc_state_ctl *ctl;
	static unsigned char *comp_data;
	struct ccci_udc_deactv_param_t *ccci_udc_deactv;
	struct ccci_udc_disc_param_t *ccci_udc_disc;
	struct sk_buff *skb_tmp;
	struct ccci_udc_actv_param_t *ccci_udc_actv;
	static struct z_stream_s zcpr0, zcpr1;
	int deflate_end_flag = 0;
	unsigned int md_write = 0, ap_read = 0;

	skb_tmp = ccci_alloc_skb(sizeof(*ccci_udc_actv), 1, 1);
	if (!skb_tmp) {
		CCCI_ERROR_LOG(0, UDC,
			"%s:alloc skb_tmp fail\n", __func__);
		return;
	}

	ctl = kzalloc(sizeof(struct udc_state_ctl), GFP_KERNEL);

	ccci_udc_actv = (struct ccci_udc_actv_param_t *)skb->data;
	udc_cmd = ccci_udc_actv->udc_cmd;
	CCCI_DEBUG_LOG(0, UDC,
		"%s++ udc_cmd:%d\n", __func__, udc_cmd);

	switch (udc_cmd) {
	case UDC_CMD_ACTV:
	{
		unsigned int buffer_size = ccci_udc_actv->buf_sz;
		enum udc_dict_opt_e dic_option = ccci_udc_actv->dict_opt;
		unsigned int inst_id  = ccci_udc_actv->udc_inst_id;

		CCCI_NORMAL_LOG(0, UDC,
			"udc_actv ins%d:cmd:%d,buf_sz:%d,dict_opt:%d\n",
			inst_id, udc_cmd, buffer_size, dic_option);

		if (inst_id == 0)
			ret = udc_actv_handler(&zcpr0, dic_option,
					buffer_size, inst_id);
		else if (inst_id == 1)
			ret = udc_actv_handler(&zcpr1, dic_option,
					buffer_size, inst_id);
		if (ret < 0)
			goto end;
		/* get sharememory info */
		region = ccci_md_get_smem_by_user_id(SMEM_USER_RAW_UDC_DATA);
		if (region) {
			uncomp_data_buf_base = (unsigned char *)
				region->base_ap_view_vir;
			comp_data_buf_base = (unsigned char *)
				(region->base_ap_view_vir + 0x500000+32);
			rw_index = (struct ap_md_rw_index *)
				(region->base_ap_view_vir + 0x500000);
			comp_data = comp_data_buf_base;
			CCCI_NORMAL_LOG(0, UDC,
				"base_md_view_phy:0x%lx,base_ap_view_phy:0x%lx\n",
				(unsigned long)region->base_md_view_phy,
				(unsigned long)region->base_ap_view_phy);
			CCCI_NORMAL_LOG(0, UDC,
				"uncomp_base:%p,comp_base:%p\n",
				uncomp_data_buf_base, comp_data_buf_base);
		} else
			CCCI_ERROR_LOG(0, UDC,
				"can not find region:SMEM_USER_RAW_UDC_DATA\n");

		region = ccci_md_get_smem_by_user_id(SMEM_USER_RAW_UDC_DESCTAB);
		if (region) {
			uncomp_cache_data_base =
				(unsigned char *)region->base_ap_view_vir;
			/* cmp_req and cmp_rslt offset:48k */
			req_des_0_base = (struct udc_comp_req_t *)
				(region->base_ap_view_vir + 0xC000);
			rslt_des_0_base = (struct udc_comp_rslt_t *)
				(region->base_ap_view_vir + 0xC000 + 0x1000);
			req_des_1_base = (struct udc_comp_req_t *)
				(region->base_ap_view_vir + 0xC000 + 0x2000);
			rslt_des_1_base = (struct udc_comp_rslt_t *)
				(region->base_ap_view_vir + 0xC000 + 0x3000);
			CCCI_NORMAL_LOG(0, UDC, "uncomp_cache_base:%p\n",
				uncomp_cache_data_base);
		} else
			CCCI_ERROR_LOG(0, UDC,
				"can not find region:SMEM_USER_RAW_UDC_DESCTAB\n");
		break;
	}
	case UDC_CMD_DEACTV:
	{
		unsigned int inst_id;

deactive_exit:
		ccci_udc_deactv =
			(struct ccci_udc_deactv_param_t *)skb->data;

		udc_cmd = ccci_udc_deactv->udc_cmd;
		inst_id = ccci_udc_deactv->udc_inst_id;
		CCCI_NORMAL_LOG(0, UDC,
			"deactv ins%d:udc_cmd:%d\n",
			inst_id, udc_cmd);

		if (inst_id == 0) {
			/* ret = udc_deactv_handler(&zcpr0, inst_id); */
			deflate_end_flag = deflateEnd_cb(&zcpr0);
			udc_deinit(&zcpr0);

		} else if (inst_id == 1) {
			/* ret = udc_deactv_handler(&zcpr1, inst_id); */
			deflate_end_flag = deflateEnd_cb(&zcpr1);
			udc_deinit(&zcpr1);
		}

		/* the continuous input is unprocessed, it maybe return -3 */
		if (deflate_end_flag < 0 && deflate_end_flag != -3) {
			ret = deflate_end_flag;
			CCCI_ERROR_LOG(0, UDC, "deflateEnd_ins%d,ret:%d\n",
				inst_id, deflate_end_flag);
		}

		ctl->curr_state = atomic_read(&udc_status);
		if (ctl->curr_state == UDC_DEACTV ||
			ctl->curr_state == UDC_DEACTV_DONE) {
			atomic_set(&udc_status, UDC_IDLE);
			ctl->last_state = ctl->curr_state;
			ctl->curr_state = UDC_IDLE;
		}
		break;
	}
	case UDC_CMD_DISC:
	{
		unsigned int inst_id;
		unsigned int new_req_r;

discard_req:
		ccci_udc_disc =
			(struct ccci_udc_disc_param_t *)skb->data;
		udc_cmd = ccci_udc_disc->udc_cmd;
		new_req_r = ccci_udc_disc->new_req_r;
		inst_id = ccci_udc_disc->udc_inst_id;
		CCCI_NORMAL_LOG(0, UDC,
			"disc ins%d:udc_cmd:%d,new_req_r:%d\n",
			inst_id, udc_cmd, new_req_r);

		if (inst_id == 0) {
			ap_read = rw_index->md_des_ins0.read;
			rw_index->md_des_ins0.read =
				ccci_udc_disc->new_req_r;
			CCCI_NORMAL_LOG(0, UDC,
				"ins%d update ap read:from %d to %d\n",
				inst_id, ap_read, rw_index->md_des_ins0.read);
		} else if (inst_id == 1) {
			ap_read = rw_index->md_des_ins1.read;
			rw_index->md_des_ins1.read =
				ccci_udc_disc->new_req_r;
			CCCI_NORMAL_LOG(0, UDC,
				"ins%d update ap read:from %d to %d\n",
				inst_id, ap_read, rw_index->md_des_ins1.read);
		}
		ctl->curr_state = atomic_read(&udc_status);
		if (ctl->curr_state == UDC_DISCARD) {
			atomic_set(&udc_status, UDC_IDLE);
			ctl->last_state = ctl->curr_state;
			ctl->curr_state = UDC_IDLE;
		}
		break;
	}
	case UDC_CMD_KICK:
	{
		unsigned int inst_id, exp_timer;
		struct udc_comp_req_t *req_des, *req_des_base;
		struct ccci_udc_kick_param_t *ccci_udc_kick;

retry_kick:
		ccci_udc_kick =
			(struct ccci_udc_kick_param_t *)skb->data;
		inst_id = ccci_udc_kick->udc_inst_id;
		udc_cmd = ccci_udc_kick->udc_cmd;
		/* to do exp_timer does not work now */
		exp_timer = ccci_udc_kick->exp_tmr;

		CCCI_NORMAL_LOG(0, UDC,
			"kick ins%d:udc_cmd:%d,exp_timer:%d\n",
			inst_id, udc_cmd, exp_timer);
		if (inst_id == 0) {
			req_des_base = req_des_0_base;
			ap_read = rw_index->md_des_ins0.read;
			md_write = rw_index->md_des_ins0.write;
		} else if (inst_id == 1) {
			req_des_base = req_des_1_base;
			ap_read = rw_index->md_des_ins1.read;
			md_write = rw_index->md_des_ins1.write;
		}

		while (ap_read != md_write) {
			if (inst_id == 0) {
				req_des = req_des_base + ap_read;
				ret = udc_kick_handler(port, &zcpr0,
						inst_id, &comp_data);
				if (ret < 0) {
					CCCI_ERROR_LOG(0, UDC,
					"udc kick fail ret:%d!!\n", ret);
					goto end;
				}
				if (req_des->con == 0) {
					udc_cmd_check(port, &skb_tmp,
						&skb, inst_id, ctl);
					if (ctl->curr_state ==
						UDC_DEACTV_DONE ||
						ctl->curr_state ==
						UDC_KICKDEACTV) {
						CCCI_NORMAL_LOG(0, UDC,
						"ins%d:goto deactive_exit\n",
						inst_id);
						goto deactive_exit;
					} else if (ctl->curr_state ==
						UDC_DISC_DONE) {
						CCCI_NORMAL_LOG(0, UDC,
						"ins%d:goto discard_req\n",
						inst_id);
						goto discard_req;
					}
				}
				/* insure sequential execution */
				/* mb(); */
				ap_read = rw_index->md_des_ins0.read;
				md_write = rw_index->md_des_ins0.write;
			} else if (inst_id == 1) {
				req_des = req_des_base + ap_read;
				ret = udc_kick_handler(port, &zcpr1,
						inst_id, &comp_data);
				if (ret < 0) {
					CCCI_ERROR_LOG(0, UDC,
					"udc kick fail ret:%d!!\n", ret);
					goto end;
				}
				if (req_des->con == 0) {
					udc_cmd_check(port, &skb_tmp,
						&skb, inst_id, ctl);
					if (ctl->curr_state ==
						UDC_HandleHighKick) {
						CCCI_NORMAL_LOG(0, UDC,
						"ins%d:goto retry_kick\n",
						inst_id);
						goto retry_kick;
					} else if (ctl->curr_state ==
						UDC_DEACTV_DONE ||
						ctl->curr_state ==
						UDC_KICKDEACTV) {
						CCCI_NORMAL_LOG(0, UDC,
						"ins%d:goto deactive_exit\n",
						inst_id);
						goto deactive_exit;
					} else if (ctl->curr_state ==
						UDC_DISC_DONE) {
						CCCI_NORMAL_LOG(0, UDC,
						"ins%d:goto discard_req\n",
						inst_id);
						goto discard_req;
					}
				}

				/* insure sequential execution */
				/* mb(); */
				ap_read = rw_index->md_des_ins1.read;
				md_write = rw_index->md_des_ins1.write;
			}
		}
		break;
	}
	default:
		CCCI_ERROR_LOG(0, UDC,
			"[Error]Unknown Operation ID (0x%08X)\n",
			ccci_udc_actv->udc_cmd);
		break;
	}
end:
	/* resp_to_md */
	ret = udc_resp_msg_to_md(port, skb, ret);
	if (ret < 0)
		CCCI_ERROR_LOG(0, UDC,
			"send udc msg to md fail ret:%d!!\n", ret);
	CCCI_DEBUG_LOG(0, UDC,
		"%s-- udc_cmd:%d\n", __func__, udc_cmd);
	/* dump read write index */
	CCCI_NORMAL_LOG(0, UDC,
		"ins0:md rw:%d %d,ap rw:%d %d,ins1:md rw:%d %d,ap rw:%d %d\n",
		rw_index->md_des_ins0.read, rw_index->md_des_ins0.write,
		rw_index->ap_resp_ins0.read, rw_index->ap_resp_ins0.write,
		rw_index->md_des_ins1.read, rw_index->md_des_ins1.write,
		rw_index->ap_resp_ins1.read, rw_index->ap_resp_ins1.write);
	if (udc_restore_skb(port, ctl, &skb_tmp, &skb)) {
		CCCI_NORMAL_LOG(0, UDC,
			"restore_skb:goto retry_kick\n");
		goto retry_kick;
	}
	ccci_free_skb(skb_tmp);
	kfree(ctl);
}

static int port_udc_init(struct port_t *port)
{
	CCCI_DEBUG_LOG(0, PORT,
		"kernel port %s is initializing\n", port->name);
	port->skb_handler = &udc_cmd_handler;
	port->private_data = kthread_run(port_kthread_handler,
		port, "%s", port->name);
	port->rx_length_th = MAX_QUEUE_LENGTH;
	port->skb_from_pool = 1;
	return 0;
}

struct port_ops ccci_udc_port_ops = {
	.init = &port_udc_init,
	.recv_skb = &port_recv_skb,
};

