// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2012-2019,2021 The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include "ipa_i.h"
#include "ipahal/ipahal.h"

static const u32 ipa_hdr_bin_sz[IPA_HDR_BIN_MAX] = { 8, 16, 24, 36, 64};
static const u32 ipa_hdr_proc_ctx_bin_sz[IPA_HDR_PROC_CTX_BIN_MAX] = { 32, 64};

#define HDR_TYPE_IS_VALID(type) \
	((type) >= 0 && (type) < IPA_HDR_L2_MAX)

#define HDR_PROC_TYPE_IS_VALID(type) \
	((type) >= 0 && (type) < IPA_HDR_PROC_MAX)

/**
 * ipa3_generate_hdr_hw_tbl() - generates the headers table
 * @mem:	[out] buffer to put the header table
 *
 * Returns:	0 on success, negative on failure
 */
static int ipa3_generate_hdr_hw_tbl(struct ipa_mem_buffer *mem)
{
	struct ipa3_hdr_entry *entry;
	gfp_t flag = GFP_KERNEL;

	mem->size = ipa3_ctx->hdr_tbl.end;

	if (mem->size == 0) {
		IPAERR("hdr tbl empty\n");
		return -EPERM;
	}
	IPADBG_LOW("tbl_sz=%d\n", ipa3_ctx->hdr_tbl.end);

alloc:
	mem->base = dma_zalloc_coherent(ipa3_ctx->pdev, mem->size,
			&mem->phys_base, flag);
	if (!mem->base) {
		if (flag == GFP_KERNEL) {
			flag = GFP_ATOMIC;
			goto alloc;
		}
		IPAERR("fail to alloc DMA buff of size %d\n", mem->size);
		return -ENOMEM;
	}

	list_for_each_entry(entry, &ipa3_ctx->hdr_tbl.head_hdr_entry_list,
			link) {
		if (entry->is_hdr_proc_ctx)
			continue;
		IPADBG_LOW("hdr of len %d ofst=%d\n", entry->hdr_len,
				entry->offset_entry->offset);
		ipahal_cp_hdr_to_hw_buff(mem->base, entry->offset_entry->offset,
				entry->hdr, entry->hdr_len);
	}

	return 0;
}

static int ipa3_hdr_proc_ctx_to_hw_format(struct ipa_mem_buffer *mem,
	u64 hdr_base_addr)
{
	struct ipa3_hdr_proc_ctx_entry *entry;
	int ret;
	int ep;
	struct ipa_ep_cfg *cfg_ptr;
	struct ipa_l2tp_header_remove_procparams *l2p_hdr_rm_ptr;

	list_for_each_entry(entry,
			&ipa3_ctx->hdr_proc_ctx_tbl.head_proc_ctx_entry_list,
			link) {
		IPADBG_LOW("processing type %d ofst=%d\n",
			entry->type, entry->offset_entry->offset);

		if (entry->l2tp_params.is_dst_pipe_valid) {
			ep = ipa3_get_ep_mapping(entry->l2tp_params.dst_pipe);

			if (ep >= 0) {
				cfg_ptr = &ipa3_ctx->ep[ep].cfg;
				l2p_hdr_rm_ptr =
					&entry->l2tp_params.hdr_remove_param;
				l2p_hdr_rm_ptr->hdr_ofst_pkt_size_valid =
					cfg_ptr->hdr.hdr_ofst_pkt_size_valid;
				l2p_hdr_rm_ptr->hdr_ofst_pkt_size =
					cfg_ptr->hdr.hdr_ofst_pkt_size;
				l2p_hdr_rm_ptr->hdr_endianness =
					cfg_ptr->hdr_ext.hdr_little_endian ?
					0 : 1;
			}
		}
		/* Check the pointer and header length to avoid
		 *	 dangerous overflow in HW
		 */
		if (unlikely(!entry->hdr || !entry->hdr->offset_entry ||
				!entry->offset_entry ||
				entry->hdr->hdr_len == 0 ||
				entry->hdr->hdr_len >
				ipa_hdr_bin_sz[IPA_HDR_BIN_MAX - 1])) {
			IPAERR_RL("Found invalid hdr entry\n");
			return -EINVAL;
		}

		ret = ipahal_cp_proc_ctx_to_hw_buff(entry->type, mem->base,
				entry->offset_entry->offset,
				entry->hdr->hdr_len,
				entry->hdr->is_hdr_proc_ctx,
				entry->hdr->phys_base,
				hdr_base_addr,
				entry->hdr->offset_entry,
				&entry->l2tp_params,
				&entry->generic_params,
				ipa3_ctx->use_64_bit_dma_mask);
		if (ret)
			return ret;
	}

	return 0;
}

/**
 * ipa3_generate_hdr_proc_ctx_hw_tbl() -
 * generates the headers processing context table.
 * @mem:		[out] buffer to put the processing context table
 * @aligned_mem:	[out] actual processing context table (with alignment).
 *			Processing context table needs to be 8 Bytes aligned.
 *
 * Returns:	0 on success, negative on failure
 */
static int ipa3_generate_hdr_proc_ctx_hw_tbl(u64 hdr_sys_addr,
	struct ipa_mem_buffer *mem, struct ipa_mem_buffer *aligned_mem)
{
	u64 hdr_base_addr;
	gfp_t flag = GFP_KERNEL;
	int ret;

	mem->size = (ipa3_ctx->hdr_proc_ctx_tbl.end) ? : 4;

	/* make sure table is aligned */
	mem->size += IPA_HDR_PROC_CTX_TABLE_ALIGNMENT_BYTE;

	IPADBG_LOW("tbl_sz=%d\n", ipa3_ctx->hdr_proc_ctx_tbl.end);

alloc:
	mem->base = dma_alloc_coherent(ipa3_ctx->pdev, mem->size,
			&mem->phys_base, flag);
	if (!mem->base) {
		if (flag == GFP_KERNEL) {
			flag = GFP_ATOMIC;
			goto alloc;
		}
		IPAERR("fail to alloc DMA buff of size %d\n", mem->size);
		return -ENOMEM;
	}

	aligned_mem->phys_base =
		IPA_HDR_PROC_CTX_TABLE_ALIGNMENT(mem->phys_base);
	aligned_mem->base = mem->base +
		(aligned_mem->phys_base - mem->phys_base);
	aligned_mem->size = mem->size - IPA_HDR_PROC_CTX_TABLE_ALIGNMENT_BYTE;
	memset(aligned_mem->base, 0, aligned_mem->size);
	hdr_base_addr = (ipa3_ctx->hdr_tbl_lcl) ? IPA_MEM_PART(apps_hdr_ofst) :
		hdr_sys_addr;
	ret = ipa3_hdr_proc_ctx_to_hw_format(aligned_mem, hdr_base_addr);
	if (ret) {
		dma_free_coherent(ipa3_ctx->pdev, mem->size, mem->base, mem->phys_base);
		return ret;
	}
	return ret;
}

/**
 * __ipa_commit_hdr_v3_0() - Commits the header table from memory to HW
 *
 * Returns:	0 on success, negative on failure
 */
int __ipa_commit_hdr_v3_0(void)
{
	struct ipa3_desc desc[3];
	struct ipa_mem_buffer hdr_mem;
	struct ipa_mem_buffer ctx_mem;
	struct ipa_mem_buffer aligned_ctx_mem;
	struct ipahal_imm_cmd_dma_shared_mem dma_cmd_hdr = {0};
	struct ipahal_imm_cmd_dma_shared_mem dma_cmd_ctx = {0};
	struct ipahal_imm_cmd_register_write reg_write_cmd = {0};
	struct ipahal_imm_cmd_hdr_init_system hdr_init_cmd = {0};
	struct ipahal_imm_cmd_pyld *hdr_cmd_pyld = NULL;
	struct ipahal_imm_cmd_pyld *ctx_cmd_pyld = NULL;
	struct ipahal_imm_cmd_pyld *coal_cmd_pyld = NULL;
	int rc = -EFAULT;
	int i;
	int num_cmd = 0;
	u32 proc_ctx_size;
	u32 proc_ctx_ofst;
	u32 proc_ctx_size_ddr;
	struct ipahal_imm_cmd_register_write reg_write_coal_close;
	struct ipahal_reg_valmask valmask;

	memset(desc, 0, 3 * sizeof(struct ipa3_desc));

	if (ipa3_generate_hdr_hw_tbl(&hdr_mem)) {
		IPAERR("fail to generate HDR HW TBL\n");
		goto failure_hdr;
	}

	if (ipa3_generate_hdr_proc_ctx_hw_tbl(hdr_mem.phys_base, &ctx_mem,
	    &aligned_ctx_mem)) {
		IPAERR("fail to generate HDR PROC CTX HW TBL\n");
		goto failure_hdr_proc;
	}

	/* IC to close the coal frame before HPS Clear if coal is enabled */
	if (ipa3_get_ep_mapping(IPA_CLIENT_APPS_WAN_COAL_CONS) != -1) {
		i = ipa3_get_ep_mapping(IPA_CLIENT_APPS_WAN_COAL_CONS);
		reg_write_coal_close.skip_pipeline_clear = false;
		reg_write_coal_close.pipeline_clear_options = IPAHAL_HPS_CLEAR;
		reg_write_coal_close.offset = ipahal_get_reg_ofst(
			IPA_AGGR_FORCE_CLOSE);
		ipahal_get_aggr_force_close_valmask(i, &valmask);
		reg_write_coal_close.value = valmask.val;
		reg_write_coal_close.value_mask = valmask.mask;
		coal_cmd_pyld = ipahal_construct_imm_cmd(
			IPA_IMM_CMD_REGISTER_WRITE,
			&reg_write_coal_close, false);
		if (!coal_cmd_pyld) {
			IPAERR("failed to construct coal close IC\n");
			goto end;
		}
		ipa3_init_imm_cmd_desc(&desc[num_cmd], coal_cmd_pyld);
		++num_cmd;
	}

	if (ipa3_ctx->hdr_tbl_lcl) {
		if (hdr_mem.size > IPA_MEM_PART(apps_hdr_size)) {
			IPAERR("tbl too big needed %d avail %d\n", hdr_mem.size,
				IPA_MEM_PART(apps_hdr_size));
			goto end;
		} else {
			dma_cmd_hdr.is_read = false; /* write operation */
			dma_cmd_hdr.skip_pipeline_clear = false;
			dma_cmd_hdr.pipeline_clear_options = IPAHAL_HPS_CLEAR;
			dma_cmd_hdr.system_addr = hdr_mem.phys_base;
			dma_cmd_hdr.size = hdr_mem.size;
			dma_cmd_hdr.local_addr =
				ipa3_ctx->smem_restricted_bytes +
				IPA_MEM_PART(apps_hdr_ofst);
			hdr_cmd_pyld = ipahal_construct_imm_cmd(
				IPA_IMM_CMD_DMA_SHARED_MEM,
				&dma_cmd_hdr, false);
			if (!hdr_cmd_pyld) {
				IPAERR("fail construct dma_shared_mem cmd\n");
				goto end;
			}
		}
	} else {
		if (hdr_mem.size > IPA_MEM_PART(apps_hdr_size_ddr)) {
			IPAERR("tbl too big needed %d avail %d\n", hdr_mem.size,
				IPA_MEM_PART(apps_hdr_size_ddr));
			goto end;
		} else {
			hdr_init_cmd.hdr_table_addr = hdr_mem.phys_base;
			hdr_cmd_pyld = ipahal_construct_imm_cmd(
				IPA_IMM_CMD_HDR_INIT_SYSTEM,
				&hdr_init_cmd, false);
			if (!hdr_cmd_pyld) {
				IPAERR("fail construct hdr_init_system cmd\n");
				goto end;
			}
		}
	}
	ipa3_init_imm_cmd_desc(&desc[num_cmd], hdr_cmd_pyld);
	++num_cmd;
	IPA_DUMP_BUFF(hdr_mem.base, hdr_mem.phys_base, hdr_mem.size);

	proc_ctx_size = IPA_MEM_PART(apps_hdr_proc_ctx_size);
	proc_ctx_ofst = IPA_MEM_PART(apps_hdr_proc_ctx_ofst);
	if (ipa3_ctx->hdr_proc_ctx_tbl_lcl) {
		if (aligned_ctx_mem.size > proc_ctx_size) {
			IPAERR("tbl too big needed %d avail %d\n",
				aligned_ctx_mem.size,
				proc_ctx_size);
			goto end;
		} else {
			dma_cmd_ctx.is_read = false; /* Write operation */
			dma_cmd_ctx.skip_pipeline_clear = false;
			dma_cmd_ctx.pipeline_clear_options = IPAHAL_HPS_CLEAR;
			dma_cmd_ctx.system_addr = aligned_ctx_mem.phys_base;
			dma_cmd_ctx.size = aligned_ctx_mem.size;
			dma_cmd_ctx.local_addr =
				ipa3_ctx->smem_restricted_bytes +
				proc_ctx_ofst;
			ctx_cmd_pyld = ipahal_construct_imm_cmd(
				IPA_IMM_CMD_DMA_SHARED_MEM,
				&dma_cmd_ctx, false);
			if (!ctx_cmd_pyld) {
				IPAERR("fail construct dma_shared_mem cmd\n");
				goto end;
			}
		}
	} else {
		proc_ctx_size_ddr = IPA_MEM_PART(apps_hdr_proc_ctx_size_ddr);
		if (aligned_ctx_mem.size > proc_ctx_size_ddr) {
			IPAERR("tbl too big, needed %d avail %d\n",
				aligned_ctx_mem.size,
				proc_ctx_size_ddr);
			goto end;
		} else {
			reg_write_cmd.skip_pipeline_clear = false;
			reg_write_cmd.pipeline_clear_options =
				IPAHAL_HPS_CLEAR;
			reg_write_cmd.offset =
				ipahal_get_reg_ofst(
				IPA_SYS_PKT_PROC_CNTXT_BASE);
			reg_write_cmd.value = aligned_ctx_mem.phys_base;
			reg_write_cmd.value_mask =
				~(IPA_HDR_PROC_CTX_TABLE_ALIGNMENT_BYTE - 1);
			ctx_cmd_pyld = ipahal_construct_imm_cmd(
				IPA_IMM_CMD_REGISTER_WRITE,
				&reg_write_cmd, false);
			if (!ctx_cmd_pyld) {
				IPAERR("fail construct register_write cmd\n");
				goto end;
			}
		}
	}
	ipa3_init_imm_cmd_desc(&desc[num_cmd], ctx_cmd_pyld);
	++num_cmd;
	IPA_DUMP_BUFF(ctx_mem.base, ctx_mem.phys_base, ctx_mem.size);

	if (ipa3_send_cmd(num_cmd, desc))
		IPAERR("fail to send immediate command\n");
	else
		rc = 0;
end:
	if (ipa3_ctx->hdr_tbl_lcl) {
		dma_free_coherent(ipa3_ctx->pdev, hdr_mem.size, hdr_mem.base,
			hdr_mem.phys_base);
	} else {
		if (!rc) {
			if (ipa3_ctx->hdr_mem.phys_base)
				dma_free_coherent(ipa3_ctx->pdev,
				ipa3_ctx->hdr_mem.size,
				ipa3_ctx->hdr_mem.base,
				ipa3_ctx->hdr_mem.phys_base);
			ipa3_ctx->hdr_mem = hdr_mem;
		} else {
			dma_free_coherent(ipa3_ctx->pdev, hdr_mem.size,
			hdr_mem.base, hdr_mem.phys_base);
		}
	}

	if (ipa3_ctx->hdr_proc_ctx_tbl_lcl) {
		dma_free_coherent(ipa3_ctx->pdev, ctx_mem.size, ctx_mem.base,
			ctx_mem.phys_base);
	} else {
		if (!rc) {
			if (ipa3_ctx->hdr_proc_ctx_mem.phys_base)
				dma_free_coherent(ipa3_ctx->pdev,
					ipa3_ctx->hdr_proc_ctx_mem.size,
					ipa3_ctx->hdr_proc_ctx_mem.base,
					ipa3_ctx->hdr_proc_ctx_mem.phys_base);
			ipa3_ctx->hdr_proc_ctx_mem = ctx_mem;
		} else {
			dma_free_coherent(ipa3_ctx->pdev, ctx_mem.size,
			ctx_mem.base, ctx_mem.phys_base);
		}
	}

	if (coal_cmd_pyld)
		ipahal_destroy_imm_cmd(coal_cmd_pyld);

	if (ctx_cmd_pyld)
		ipahal_destroy_imm_cmd(ctx_cmd_pyld);

	if (hdr_cmd_pyld)
		ipahal_destroy_imm_cmd(hdr_cmd_pyld);

	return rc;

failure_hdr_proc:
	dma_free_coherent(ipa3_ctx->pdev, hdr_mem.size, hdr_mem.base,
			hdr_mem.phys_base);
failure_hdr:
	return rc;
}

static int __ipa_add_hdr_proc_ctx(struct ipa_hdr_proc_ctx_add *proc_ctx,
	bool add_ref_hdr, bool user_only)
{
	struct ipa3_hdr_entry *hdr_entry;
	struct ipa3_hdr_proc_ctx_entry *entry;
	struct ipa3_hdr_proc_ctx_offset_entry *offset;
	u32 bin;
	struct ipa3_hdr_proc_ctx_tbl *htbl = &ipa3_ctx->hdr_proc_ctx_tbl;
	int id;
	int needed_len;
	int mem_size;

	IPADBG_LOW("Add processing type %d hdr_hdl %d\n",
		proc_ctx->type, proc_ctx->hdr_hdl);

	if (!HDR_PROC_TYPE_IS_VALID(proc_ctx->type)) {
		IPAERR_RL("invalid processing type %d\n", proc_ctx->type);
		return -EINVAL;
	}

	hdr_entry = ipa3_id_find(proc_ctx->hdr_hdl);
	if (!hdr_entry) {
		IPAERR_RL("hdr_hdl is invalid\n");
		return -EINVAL;
	}
	if (hdr_entry->cookie != IPA_HDR_COOKIE) {
		IPAERR_RL("Invalid header cookie %u\n", hdr_entry->cookie);
		WARN_ON_RATELIMIT_IPA(1);
		return -EINVAL;
	}
	IPADBG("Associated header is name=%s is_hdr_proc_ctx=%d\n",
		hdr_entry->name, hdr_entry->is_hdr_proc_ctx);

	entry = kmem_cache_zalloc(ipa3_ctx->hdr_proc_ctx_cache, GFP_KERNEL);
	if (!entry) {
		IPAERR("failed to alloc proc_ctx object\n");
		return -ENOMEM;
	}

	INIT_LIST_HEAD(&entry->link);

	entry->type = proc_ctx->type;
	entry->hdr = hdr_entry;
	entry->l2tp_params = proc_ctx->l2tp_params;
	entry->generic_params = proc_ctx->generic_params;
	if (add_ref_hdr)
		hdr_entry->ref_cnt++;
	entry->cookie = IPA_PROC_HDR_COOKIE;
	entry->ipacm_installed = user_only;

	needed_len = ipahal_get_proc_ctx_needed_len(proc_ctx->type);
	if ((needed_len < 0) ||
		((needed_len > ipa_hdr_proc_ctx_bin_sz[IPA_HDR_PROC_CTX_BIN0])
			&&
			(needed_len >
			ipa_hdr_proc_ctx_bin_sz[IPA_HDR_PROC_CTX_BIN1]))) {
		IPAERR_RL("unexpected needed len %d\n", needed_len);
		WARN_ON_RATELIMIT_IPA(1);
		goto bad_len;
	}

	if (needed_len <= ipa_hdr_proc_ctx_bin_sz[IPA_HDR_PROC_CTX_BIN0])
		bin = IPA_HDR_PROC_CTX_BIN0;
	else
		bin = IPA_HDR_PROC_CTX_BIN1;

	mem_size = (ipa3_ctx->hdr_proc_ctx_tbl_lcl) ?
		IPA_MEM_PART(apps_hdr_proc_ctx_size) :
		IPA_MEM_PART(apps_hdr_proc_ctx_size_ddr);
	if (list_empty(&htbl->head_free_offset_list[bin])) {
		if (htbl->end + ipa_hdr_proc_ctx_bin_sz[bin] > mem_size) {
			IPAERR_RL("hdr proc ctx table overflow\n");
			goto bad_len;
		}

		offset = kmem_cache_zalloc(ipa3_ctx->hdr_proc_ctx_offset_cache,
					   GFP_KERNEL);
		if (!offset) {
			IPAERR("failed to alloc offset object\n");
			goto bad_len;
		}
		INIT_LIST_HEAD(&offset->link);
		/*
		 * for a first item grow, set the bin and offset which are set
		 * in stone
		 */
		offset->offset = htbl->end;
		offset->bin = bin;
		offset->ipacm_installed = user_only;
		htbl->end += ipa_hdr_proc_ctx_bin_sz[bin];
		list_add(&offset->link,
				&htbl->head_offset_list[bin]);
	} else {
		/* get the first free slot */
		offset =
		    list_first_entry(&htbl->head_free_offset_list[bin],
				struct ipa3_hdr_proc_ctx_offset_entry, link);
		offset->ipacm_installed = user_only;
		list_move(&offset->link, &htbl->head_offset_list[bin]);
	}

	entry->offset_entry = offset;
	list_add(&entry->link, &htbl->head_proc_ctx_entry_list);
	htbl->proc_ctx_cnt++;
	IPADBG("add proc ctx of sz=%d cnt=%d ofst=%d\n", needed_len,
			htbl->proc_ctx_cnt, offset->offset);

	id = ipa3_id_alloc(entry);
	if (id < 0) {
		IPAERR_RL("failed to alloc id\n");
		WARN_ON_RATELIMIT_IPA(1);
		goto ipa_insert_failed;
	}
	entry->id = id;
	proc_ctx->proc_ctx_hdl = id;
	entry->ref_cnt++;

	return 0;

ipa_insert_failed:
	list_move(&offset->link,
		&htbl->head_free_offset_list[offset->bin]);
	entry->offset_entry = NULL;
	list_del(&entry->link);
	htbl->proc_ctx_cnt--;

bad_len:
	if (add_ref_hdr)
		hdr_entry->ref_cnt--;
	entry->cookie = 0;
	kmem_cache_free(ipa3_ctx->hdr_proc_ctx_cache, entry);
	return -EPERM;
}


static int __ipa_add_hdr(struct ipa_hdr_add *hdr, bool user)
{
	struct ipa3_hdr_entry *entry;
	struct ipa_hdr_offset_entry *offset = NULL;
	u32 bin;
	struct ipa3_hdr_tbl *htbl = &ipa3_ctx->hdr_tbl;
	int id;
	int mem_size;

	if (hdr->hdr_len == 0 || hdr->hdr_len > IPA_HDR_MAX_SIZE) {
		IPAERR_RL("bad param\n");
		goto error;
	}

	if (!HDR_TYPE_IS_VALID(hdr->type)) {
		IPAERR_RL("invalid hdr type %d\n", hdr->type);
		goto error;
	}

	entry = kmem_cache_zalloc(ipa3_ctx->hdr_cache, GFP_KERNEL);
	if (!entry)
		goto error;

	INIT_LIST_HEAD(&entry->link);

	memcpy(entry->hdr, hdr->hdr, hdr->hdr_len);
	entry->hdr_len = hdr->hdr_len;
	strlcpy(entry->name, hdr->name, IPA_RESOURCE_NAME_MAX);
	entry->is_partial = hdr->is_partial;
	entry->type = hdr->type;
	entry->is_eth2_ofst_valid = hdr->is_eth2_ofst_valid;
	entry->eth2_ofst = hdr->eth2_ofst;
	entry->cookie = IPA_HDR_COOKIE;
	entry->ipacm_installed = user;

	if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN0])
		bin = IPA_HDR_BIN0;
	else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN1])
		bin = IPA_HDR_BIN1;
	else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN2])
		bin = IPA_HDR_BIN2;
	else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN3])
		bin = IPA_HDR_BIN3;
	else if (hdr->hdr_len <= ipa_hdr_bin_sz[IPA_HDR_BIN4])
		bin = IPA_HDR_BIN4;
	else {
		IPAERR_RL("unexpected hdr len %d\n", hdr->hdr_len);
		goto bad_hdr_len;
	}

	mem_size = (ipa3_ctx->hdr_tbl_lcl) ? IPA_MEM_PART(apps_hdr_size) :
		IPA_MEM_PART(apps_hdr_size_ddr);

	if (list_empty(&htbl->head_free_offset_list[bin])) {
		/* if header does not fit to table, place it in DDR */
		if (htbl->end + ipa_hdr_bin_sz[bin] > mem_size) {
			entry->is_hdr_proc_ctx = true;
			entry->phys_base = dma_map_single(ipa3_ctx->pdev,
				entry->hdr,
				entry->hdr_len,
				DMA_TO_DEVICE);
			if (dma_mapping_error(ipa3_ctx->pdev,
				entry->phys_base)) {
				IPAERR("dma_map_single failure for entry\n");
				goto fail_dma_mapping;
			}
		} else {
			entry->is_hdr_proc_ctx = false;
			offset = kmem_cache_zalloc(ipa3_ctx->hdr_offset_cache,
						   GFP_KERNEL);
			if (!offset) {
				IPAERR("failed to alloc hdr offset object\n");
				goto bad_hdr_len;
			}
			INIT_LIST_HEAD(&offset->link);
			/*
			 * for a first item grow, set the bin and offset which
			 * are set in stone
			 */
			offset->offset = htbl->end;
			offset->bin = bin;
			htbl->end += ipa_hdr_bin_sz[bin];
			list_add(&offset->link,
					&htbl->head_offset_list[bin]);
			entry->offset_entry = offset;
			offset->ipacm_installed = user;
		}
	} else {
		entry->is_hdr_proc_ctx = false;
		/* get the first free slot */
		offset = list_first_entry(&htbl->head_free_offset_list[bin],
			struct ipa_hdr_offset_entry, link);
		list_move(&offset->link, &htbl->head_offset_list[bin]);
		entry->offset_entry = offset;
		offset->ipacm_installed = user;
	}

	list_add(&entry->link, &htbl->head_hdr_entry_list);
	htbl->hdr_cnt++;
	if (entry->is_hdr_proc_ctx)
		IPADBG("add hdr of sz=%d hdr_cnt=%d phys_base=%pa\n",
			hdr->hdr_len,
			htbl->hdr_cnt,
			&entry->phys_base);
	else
		IPADBG("add hdr of sz=%d hdr_cnt=%d ofst=%d\n",
			hdr->hdr_len,
			htbl->hdr_cnt,
			entry->offset_entry->offset);

	id = ipa3_id_alloc(entry);
	if (id < 0) {
		IPAERR_RL("failed to alloc id\n");
		WARN_ON_RATELIMIT_IPA(1);
		goto ipa_insert_failed;
	}
	entry->id = id;
	hdr->hdr_hdl = id;
	entry->ref_cnt++;

	if (entry->is_hdr_proc_ctx) {
		struct ipa_hdr_proc_ctx_add proc_ctx;

		IPADBG("adding processing context for header %s\n", hdr->name);
		proc_ctx.type = IPA_HDR_PROC_NONE;
		proc_ctx.hdr_hdl = id;
		if (__ipa_add_hdr_proc_ctx(&proc_ctx, false, user)) {
			IPAERR("failed to add hdr proc ctx\n");
			goto fail_add_proc_ctx;
		}
		entry->proc_ctx = ipa3_id_find(proc_ctx.proc_ctx_hdl);
	}

	return 0;

fail_add_proc_ctx:
	entry->ref_cnt--;
	hdr->hdr_hdl = 0;
	ipa3_id_remove(id);
ipa_insert_failed:
	if (entry->is_hdr_proc_ctx) {
		dma_unmap_single(ipa3_ctx->pdev, entry->phys_base,
			entry->hdr_len, DMA_TO_DEVICE);
	} else {
		if (offset)
			list_move(&offset->link,
			&htbl->head_free_offset_list[offset->bin]);
		entry->offset_entry = NULL;
	}
	htbl->hdr_cnt--;
	list_del(&entry->link);

fail_dma_mapping:
	entry->is_hdr_proc_ctx = false;

bad_hdr_len:
	entry->cookie = 0;
	kmem_cache_free(ipa3_ctx->hdr_cache, entry);
error:
	return -EPERM;
}

static int __ipa3_del_hdr_proc_ctx(u32 proc_ctx_hdl,
	bool release_hdr, bool by_user)
{
	struct ipa3_hdr_proc_ctx_entry *entry;
	struct ipa3_hdr_proc_ctx_tbl *htbl = &ipa3_ctx->hdr_proc_ctx_tbl;

	entry = ipa3_id_find(proc_ctx_hdl);
	if (!entry || (entry->cookie != IPA_PROC_HDR_COOKIE)) {
		IPAERR_RL("bad param\n");
		return -EINVAL;
	}

	IPADBG("del proc ctx cnt=%d ofst=%d\n",
		htbl->proc_ctx_cnt, entry->offset_entry->offset);

	if (by_user && entry->user_deleted) {
		IPAERR_RL("proc_ctx already deleted by user\n");
		return -EINVAL;
	}

	if (by_user)
		entry->user_deleted = true;

	if (--entry->ref_cnt) {
		IPADBG("proc_ctx_hdl %x ref_cnt %d\n",
			proc_ctx_hdl, entry->ref_cnt);
		return 0;
	}

	if (release_hdr)
		__ipa3_del_hdr(entry->hdr->id, false);

	/* move the offset entry to appropriate free list */
	list_move(&entry->offset_entry->link,
		&htbl->head_free_offset_list[entry->offset_entry->bin]);
	list_del(&entry->link);
	htbl->proc_ctx_cnt--;
	entry->cookie = 0;
	kmem_cache_free(ipa3_ctx->hdr_proc_ctx_cache, entry);

	/* remove the handle from the database */
	ipa3_id_remove(proc_ctx_hdl);

	return 0;
}

int __ipa3_del_hdr(u32 hdr_hdl, bool by_user)
{
	struct ipa3_hdr_entry *entry;
	struct ipa3_hdr_tbl *htbl = &ipa3_ctx->hdr_tbl;

	entry = ipa3_id_find(hdr_hdl);
	if (entry == NULL) {
		IPAERR_RL("lookup failed\n");
		return -EINVAL;
	}

	if (entry->cookie != IPA_HDR_COOKIE) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}

	if (entry->is_hdr_proc_ctx)
		IPADBG("del hdr of len=%d hdr_cnt=%d phys_base=%pa\n",
			entry->hdr_len, htbl->hdr_cnt, &entry->phys_base);
	else
		IPADBG("del hdr of len=%d hdr_cnt=%d ofst=%d\n",
			entry->hdr_len, htbl->hdr_cnt,
			entry->offset_entry->offset);

	if (by_user && entry->user_deleted) {
		IPAERR_RL("proc_ctx already deleted by user\n");
		return -EINVAL;
	}

	if (by_user) {
		if (!strcmp(entry->name, IPA_LAN_RX_HDR_NAME)) {
			IPADBG("Trying to delete hdr %s offset=%u\n",
				entry->name, entry->offset_entry->offset);
			if (!entry->offset_entry->offset) {
				IPAERR_RL(
				"User cannot delete default header\n");
				return -EPERM;
			}
		}
		entry->user_deleted = true;
	}

	if (--entry->ref_cnt) {
		IPADBG("hdr_hdl %x ref_cnt %d\n", hdr_hdl, entry->ref_cnt);
		return 0;
	}

	if (entry->is_hdr_proc_ctx || entry->proc_ctx) {
		dma_unmap_single(ipa3_ctx->pdev,
			entry->phys_base,
			entry->hdr_len,
			DMA_TO_DEVICE);
		__ipa3_del_hdr_proc_ctx(entry->proc_ctx->id, false, false);
	} else {
		/* move the offset entry to appropriate free list */
		list_move(&entry->offset_entry->link,
			&htbl->head_free_offset_list[entry->offset_entry->bin]);
	}
	list_del(&entry->link);
	htbl->hdr_cnt--;
	entry->cookie = 0;
	kmem_cache_free(ipa3_ctx->hdr_cache, entry);

	/* remove the handle from the database */
	ipa3_id_remove(hdr_hdl);

	return 0;
}

/**
 * ipa3_add_hdr() - add the specified headers to SW and optionally commit them
 * to IPA HW
 * @hdrs:	[inout] set of headers to add
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_add_hdr(struct ipa_ioc_add_hdr *hdrs)
{
	return ipa3_add_hdr_usr(hdrs, false);
}

/**
 * ipa3_add_hdr_usr() - add the specified headers to SW
 * and optionally commit them to IPA HW
 * @hdrs:		[inout] set of headers to add
 * @user_only:	[in] indicate installed from user
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_add_hdr_usr(struct ipa_ioc_add_hdr *hdrs, bool user_only)
{
	int i;
	int result = -EFAULT;

	if (hdrs == NULL || hdrs->num_hdrs == 0) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}

	mutex_lock(&ipa3_ctx->lock);
	IPADBG("adding %d headers to IPA driver internal data struct\n",
			hdrs->num_hdrs);
	for (i = 0; i < hdrs->num_hdrs; i++) {
		if (__ipa_add_hdr(&hdrs->hdr[i], user_only)) {
			IPAERR_RL("failed to add hdr %d\n", i);
			hdrs->hdr[i].status = -1;
		} else {
			hdrs->hdr[i].status = 0;
		}
	}

	if (hdrs->commit) {
		IPADBG("committing all headers to IPA core");
		if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
			result = -EPERM;
			goto bail;
		}
	}
	result = 0;
bail:
	mutex_unlock(&ipa3_ctx->lock);
	return result;
}

/**
 * ipa3_del_hdr_by_user() - Remove the specified headers
 * from SW and optionally commit them to IPA HW
 * @hdls:	[inout] set of headers to delete
 * @by_user:	Operation requested by user?
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_del_hdr_by_user(struct ipa_ioc_del_hdr *hdls, bool by_user)
{
	int i;
	int result = -EFAULT;

	if (hdls == NULL || hdls->num_hdls == 0) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}

	mutex_lock(&ipa3_ctx->lock);
	for (i = 0; i < hdls->num_hdls; i++) {
		if (__ipa3_del_hdr(hdls->hdl[i].hdl, by_user)) {
			IPAERR_RL("failed to del hdr %i\n", i);
			hdls->hdl[i].status = -1;
		} else {
			hdls->hdl[i].status = 0;
		}
	}

	if (hdls->commit) {
		if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
			result = -EPERM;
			goto bail;
		}
	}
	result = 0;
bail:
	mutex_unlock(&ipa3_ctx->lock);
	return result;
}

/**
 * ipa3_del_hdr() - Remove the specified headers from SW
 * and optionally commit them to IPA HW
 * @hdls:	[inout] set of headers to delete
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_del_hdr(struct ipa_ioc_del_hdr *hdls)
{
	return ipa3_del_hdr_by_user(hdls, false);
}

/**
 * ipa3_add_hdr_proc_ctx() - add the specified headers to SW
 * and optionally commit them to IPA HW
 * @proc_ctxs:	[inout] set of processing context headers to add
 * @user_only:	[in] indicate installed by user-space module
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_add_hdr_proc_ctx(struct ipa_ioc_add_hdr_proc_ctx *proc_ctxs,
							bool user_only)
{
	int i;
	int result = -EFAULT;

	if (proc_ctxs == NULL || proc_ctxs->num_proc_ctxs == 0) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}

	mutex_lock(&ipa3_ctx->lock);
	IPADBG("adding %d header processing contextes to IPA driver\n",
			proc_ctxs->num_proc_ctxs);
	for (i = 0; i < proc_ctxs->num_proc_ctxs; i++) {
		if (__ipa_add_hdr_proc_ctx(&proc_ctxs->proc_ctx[i],
				true, user_only)) {
			IPAERR_RL("failed to add hdr proc ctx %d\n", i);
			proc_ctxs->proc_ctx[i].status = -1;
		} else {
			proc_ctxs->proc_ctx[i].status = 0;
		}
	}

	if (proc_ctxs->commit) {
		IPADBG("committing all headers to IPA core");
		if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
			result = -EPERM;
			goto bail;
		}
	}
	result = 0;
bail:
	mutex_unlock(&ipa3_ctx->lock);
	return result;
}

/**
 * ipa3_del_hdr_proc_ctx_by_user() -
 * Remove the specified processing context headers from SW and
 * optionally commit them to IPA HW.
 * @hdls:	[inout] set of processing context headers to delete
 * @by_user:	Operation requested by user?
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_del_hdr_proc_ctx_by_user(struct ipa_ioc_del_hdr_proc_ctx *hdls,
	bool by_user)
{
	int i;
	int result;

	if (hdls == NULL || hdls->num_hdls == 0) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}

	mutex_lock(&ipa3_ctx->lock);
	for (i = 0; i < hdls->num_hdls; i++) {
		if (__ipa3_del_hdr_proc_ctx(hdls->hdl[i].hdl, true, by_user)) {
			IPAERR_RL("failed to del hdr %i\n", i);
			hdls->hdl[i].status = -1;
		} else {
			hdls->hdl[i].status = 0;
		}
	}

	if (hdls->commit) {
		if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
			result = -EPERM;
			goto bail;
		}
	}
	result = 0;
bail:
	mutex_unlock(&ipa3_ctx->lock);
	return result;
}

/**
 * ipa3_del_hdr_proc_ctx() -
 * Remove the specified processing context headers from SW and
 * optionally commit them to IPA HW.
 * @hdls:	[inout] set of processing context headers to delete
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_del_hdr_proc_ctx(struct ipa_ioc_del_hdr_proc_ctx *hdls)
{
	return ipa3_del_hdr_proc_ctx_by_user(hdls, false);
}

/**
 * ipa3_commit_hdr() - commit to IPA HW the current header table in SW
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_commit_hdr(void)
{
	int result = -EFAULT;

	/*
	 * issue a commit on the routing module since routing rules point to
	 * header table entries
	 */
	if (ipa3_commit_rt(IPA_IP_v4))
		return -EPERM;
	if (ipa3_commit_rt(IPA_IP_v6))
		return -EPERM;

	mutex_lock(&ipa3_ctx->lock);
	if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
		result = -EPERM;
		goto bail;
	}
	result = 0;
bail:
	mutex_unlock(&ipa3_ctx->lock);
	return result;
}

/**
 * ipa3_reset_hdr() - reset the current header table in SW (does not commit to
 * HW)
 *
 * @user_only:	[in] indicate delete rules installed by userspace
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_reset_hdr(bool user_only)
{
	struct ipa3_hdr_entry *entry;
	struct ipa3_hdr_entry *next;
	struct ipa3_hdr_proc_ctx_entry *ctx_entry;
	struct ipa3_hdr_proc_ctx_entry *ctx_next;
	struct ipa_hdr_offset_entry *off_entry;
	struct ipa_hdr_offset_entry *off_next;
	struct ipa3_hdr_proc_ctx_offset_entry *ctx_off_entry;
	struct ipa3_hdr_proc_ctx_offset_entry *ctx_off_next;
	struct ipa3_hdr_tbl *htbl = &ipa3_ctx->hdr_tbl;
	struct ipa3_hdr_proc_ctx_tbl *htbl_proc = &ipa3_ctx->hdr_proc_ctx_tbl;
	int i;

	/*
	 * issue a reset on the routing module since routing rules point to
	 * header table entries
	 */
	if (ipa3_reset_rt(IPA_IP_v4, user_only))
		IPAERR_RL("fail to reset v4 rt\n");
	if (ipa3_reset_rt(IPA_IP_v6, user_only))
		IPAERR_RL("fail to reset v6 rt\n");

	mutex_lock(&ipa3_ctx->lock);
	IPADBG("reset hdr\n");
	list_for_each_entry_safe(entry, next,
			&ipa3_ctx->hdr_tbl.head_hdr_entry_list, link) {

		/* do not remove the default header */
		if (!strcmp(entry->name, IPA_LAN_RX_HDR_NAME)) {
			IPADBG("Trying to remove hdr %s offset=%u\n",
				entry->name, entry->offset_entry->offset);
			if (!entry->offset_entry->offset) {
				if (entry->is_hdr_proc_ctx) {
					IPAERR("default header is proc ctx\n");
					mutex_unlock(&ipa3_ctx->lock);
					WARN_ON_RATELIMIT_IPA(1);
					return -EFAULT;
				}
				IPADBG("skip default header\n");
				continue;
			}
		}

		if (ipa3_id_find(entry->id) == NULL) {
			mutex_unlock(&ipa3_ctx->lock);
			IPAERR_RL("Invalid header ID\n");
			WARN_ON_RATELIMIT_IPA(1);
			return -EFAULT;
		}

		if (!user_only || entry->ipacm_installed) {
			if (entry->is_hdr_proc_ctx) {
				dma_unmap_single(ipa3_ctx->pdev,
					entry->phys_base,
					entry->hdr_len,
					DMA_TO_DEVICE);
				entry->proc_ctx->hdr = NULL;
				entry->proc_ctx = NULL;
			} else {
				/* move the offset entry to free list */
				entry->offset_entry->ipacm_installed = false;
				list_move(&entry->offset_entry->link,
				&htbl->head_free_offset_list[
					entry->offset_entry->bin]);
			}
			list_del(&entry->link);
			htbl->hdr_cnt--;
			entry->ref_cnt = 0;
			entry->cookie = 0;

			/* remove the handle from the database */
			ipa3_id_remove(entry->id);
			kmem_cache_free(ipa3_ctx->hdr_cache, entry);
		}
	}

	/* only clean up offset_list and free_offset_list on global reset */
	if (!user_only) {
		for (i = 0; i < IPA_HDR_BIN_MAX; i++) {
			list_for_each_entry_safe(off_entry, off_next,
					 &ipa3_ctx->hdr_tbl.head_offset_list[i],
					 link) {
				/**
				 * do not remove the default exception
				 * header which is at offset 0
				 */
				if (off_entry->offset == 0)
					continue;
				list_del(&off_entry->link);
				kmem_cache_free(ipa3_ctx->hdr_offset_cache,
					off_entry);
			}
			list_for_each_entry_safe(off_entry, off_next,
				&ipa3_ctx->hdr_tbl.head_free_offset_list[i],
				link) {
				list_del(&off_entry->link);
				kmem_cache_free(ipa3_ctx->hdr_offset_cache,
					off_entry);
			}
		}
		/* there is one header of size 8 */
		ipa3_ctx->hdr_tbl.end = 8;
		ipa3_ctx->hdr_tbl.hdr_cnt = 1;
	}

	IPADBG("reset hdr proc ctx\n");
	list_for_each_entry_safe(
		ctx_entry,
		ctx_next,
		&(htbl_proc->head_proc_ctx_entry_list),
		link) {

		if (ipa3_id_find(ctx_entry->id) == NULL) {
			mutex_unlock(&ipa3_ctx->lock);
			IPAERR_RL("Invalid proc header ID\n");
			WARN_ON_RATELIMIT_IPA(1);
			return -EFAULT;
		}

		if (!user_only ||
				ctx_entry->ipacm_installed) {
			/* move the offset entry to appropriate free list */
			list_move(&ctx_entry->offset_entry->link,
				&htbl_proc->head_free_offset_list[
					ctx_entry->offset_entry->bin]);
			list_del(&ctx_entry->link);
			htbl_proc->proc_ctx_cnt--;
			ctx_entry->ref_cnt = 0;
			ctx_entry->cookie = 0;

			/* remove the handle from the database */
			ipa3_id_remove(ctx_entry->id);
			kmem_cache_free(ipa3_ctx->hdr_proc_ctx_cache,
				ctx_entry);
		}
	}
	/* only clean up offset_list and free_offset_list on global reset */
	if (!user_only) {
		for (i = 0; i < IPA_HDR_PROC_CTX_BIN_MAX; i++) {
			list_for_each_entry_safe(ctx_off_entry, ctx_off_next,
				&(htbl_proc->head_offset_list[i]), link) {
				list_del(&ctx_off_entry->link);
				kmem_cache_free(
					ipa3_ctx->hdr_proc_ctx_offset_cache,
					ctx_off_entry);
			}
			list_for_each_entry_safe(ctx_off_entry, ctx_off_next,
				&(htbl_proc->head_free_offset_list[i]), link) {
				list_del(&ctx_off_entry->link);
				kmem_cache_free(
					ipa3_ctx->hdr_proc_ctx_offset_cache,
					ctx_off_entry);
			}
		}
		htbl_proc->end = 0;
		htbl_proc->proc_ctx_cnt = 0;
	}

	/* commit the change to IPA-HW */
	if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
		IPAERR("fail to commit hdr\n");
		WARN_ON_RATELIMIT_IPA(1);
		mutex_unlock(&ipa3_ctx->lock);
		return -EFAULT;
	}

	mutex_unlock(&ipa3_ctx->lock);
	return 0;
}

static struct ipa3_hdr_entry *__ipa_find_hdr(const char *name)
{
	struct ipa3_hdr_entry *entry;

	if (strnlen(name, IPA_RESOURCE_NAME_MAX) == IPA_RESOURCE_NAME_MAX) {
		IPAERR_RL("Header name too long: %s\n", name);
		return NULL;
	}

	list_for_each_entry(entry, &ipa3_ctx->hdr_tbl.head_hdr_entry_list,
			link) {
		if (!strcmp(name, entry->name))
			return entry;
	}

	return NULL;
}

/**
 * ipa3_get_hdr() - Lookup the specified header resource
 * @lookup:	[inout] header to lookup and its handle
 *
 * lookup the specified header resource and return handle if it exists
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 *		Caller should call ipa3_put_hdr later if this function succeeds
 */
int ipa3_get_hdr(struct ipa_ioc_get_hdr *lookup)
{
	struct ipa3_hdr_entry *entry;
	int result = -1;

	if (lookup == NULL) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}
	mutex_lock(&ipa3_ctx->lock);
	lookup->name[IPA_RESOURCE_NAME_MAX-1] = '\0';
	entry = __ipa_find_hdr(lookup->name);
	if (entry) {
		lookup->hdl = entry->id;
		result = 0;
	}
	mutex_unlock(&ipa3_ctx->lock);

	return result;
}

/**
 * __ipa3_release_hdr() - drop reference to header and cause
 * deletion if reference count permits
 * @hdr_hdl:	[in] handle of header to be released
 *
 * Returns:	0 on success, negative on failure
 */
int __ipa3_release_hdr(u32 hdr_hdl)
{
	int result = 0;

	if (__ipa3_del_hdr(hdr_hdl, false)) {
		IPADBG("fail to del hdr %x\n", hdr_hdl);
		result = -EFAULT;
		goto bail;
	}

	/* commit for put */
	if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
		IPAERR("fail to commit hdr\n");
		result = -EFAULT;
		goto bail;
	}

bail:
	return result;
}

/**
 * __ipa3_release_hdr_proc_ctx() - drop reference to processing context
 *  and cause deletion if reference count permits
 * @proc_ctx_hdl:	[in] handle of processing context to be released
 *
 * Returns:	0 on success, negative on failure
 */
int __ipa3_release_hdr_proc_ctx(u32 proc_ctx_hdl)
{
	int result = 0;

	if (__ipa3_del_hdr_proc_ctx(proc_ctx_hdl, true, false)) {
		IPADBG("fail to del hdr %x\n", proc_ctx_hdl);
		result = -EFAULT;
		goto bail;
	}

	/* commit for put */
	if (ipa3_ctx->ctrl->ipa3_commit_hdr()) {
		IPAERR("fail to commit hdr\n");
		result = -EFAULT;
		goto bail;
	}

bail:
	return result;
}

/**
 * ipa3_put_hdr() - Release the specified header handle
 * @hdr_hdl:	[in] the header handle to release
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_put_hdr(u32 hdr_hdl)
{
	struct ipa3_hdr_entry *entry;
	int result = -EFAULT;

	mutex_lock(&ipa3_ctx->lock);

	entry = ipa3_id_find(hdr_hdl);
	if (entry == NULL) {
		IPAERR_RL("lookup failed\n");
		result = -EINVAL;
		goto bail;
	}

	if (entry->cookie != IPA_HDR_COOKIE) {
		IPAERR_RL("invalid header entry\n");
		result = -EINVAL;
		goto bail;
	}

	result = 0;
bail:
	mutex_unlock(&ipa3_ctx->lock);
	return result;
}

/**
 * ipa3_copy_hdr() - Lookup the specified header resource and return a copy of
 * it
 * @copy:	[inout] header to lookup and its copy
 *
 * lookup the specified header resource and return a copy of it (along with its
 * attributes) if it exists, this would be called for partial headers
 *
 * Returns:	0 on success, negative on failure
 *
 * Note:	Should not be called from atomic context
 */
int ipa3_copy_hdr(struct ipa_ioc_copy_hdr *copy)
{
	struct ipa3_hdr_entry *entry;
	int result = -EFAULT;

	if (copy == NULL) {
		IPAERR_RL("bad parm\n");
		return -EINVAL;
	}
	mutex_lock(&ipa3_ctx->lock);
	copy->name[IPA_RESOURCE_NAME_MAX-1] = '\0';
	entry = __ipa_find_hdr(copy->name);
	if (entry) {
		memcpy(copy->hdr, entry->hdr, entry->hdr_len);
		copy->hdr_len = entry->hdr_len;
		copy->type = entry->type;
		copy->is_partial = entry->is_partial;
		copy->is_eth2_ofst_valid = entry->is_eth2_ofst_valid;
		copy->eth2_ofst = entry->eth2_ofst;
		result = 0;
	}
	mutex_unlock(&ipa3_ctx->lock);

	return result;
}
