// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
 */

#include <linux/ipa.h>
#include <linux/errno.h>
#include <linux/ipc_logging.h>
#include <linux/debugfs.h>
#include "ipahal.h"
#include "ipahal_fltrt.h"
#include "ipahal_fltrt_i.h"
#include "ipahal_i.h"
#include "../../ipa_common_i.h"

#define IPA_MAC_FLT_BITS (IPA_FLT_MAC_DST_ADDR_ETHER_II | \
		IPA_FLT_MAC_SRC_ADDR_ETHER_II | IPA_FLT_MAC_DST_ADDR_802_3 | \
		IPA_FLT_MAC_SRC_ADDR_802_3 | IPA_FLT_MAC_DST_ADDR_802_1Q | \
		IPA_FLT_MAC_SRC_ADDR_802_1Q)

/*
 * struct ipahal_fltrt_obj - Flt/Rt H/W information for specific IPA version
 * @support_hash: Is hashable tables supported
 * @tbl_width: Width of table in bytes
 * @sysaddr_alignment: System table address alignment
 * @lcladdr_alignment: Local table offset alignment
 * @blk_sz_alignment: Rules block size alignment
 * @rule_start_alignment: Rule start address alignment
 * @tbl_hdr_width: Width of the header structure in bytes
 * @tbl_addr_mask: Masking for Table address
 * @rule_max_prio: Max possible priority of a rule
 * @rule_min_prio: Min possible priority of a rule
 * @low_rule_id: Low value of Rule ID that can be used
 * @rule_id_bit_len: Rule is high (MSB) bit len
 * @rule_buf_size: Max size rule may utilize.
 * @write_val_to_hdr: Write address or offset to header entry
 * @create_flt_bitmap: Create bitmap in H/W format using given bitmap
 * @create_tbl_addr: Given raw table address, create H/W formated one
 * @parse_tbl_addr: Parse the given H/W address (hdr format)
 * @rt_generate_hw_rule: Generate RT rule in H/W format
 * @flt_generate_hw_rule: Generate FLT rule in H/W format
 * @flt_generate_eq: Generate flt equation attributes from rule attributes
 * @rt_parse_hw_rule: Parse rt rule read from H/W
 * @flt_parse_hw_rule: Parse flt rule read from H/W
 * @eq_bitfield: Array of the bit fields of the support equations.
 *	0xFF means the equation is not supported
 * @prefetech_buf_size: Prefetch buf size;
 */
struct ipahal_fltrt_obj {
	bool support_hash;
	u32 tbl_width;
	u32 sysaddr_alignment;
	u32 lcladdr_alignment;
	u32 blk_sz_alignment;
	u32 rule_start_alignment;
	u32 tbl_hdr_width;
	u32 tbl_addr_mask;
	int rule_max_prio;
	int rule_min_prio;
	u32 low_rule_id;
	u32 rule_id_bit_len;
	u32 rule_buf_size;
	u8* (*write_val_to_hdr)(u64 val, u8 *hdr);
	u64 (*create_flt_bitmap)(u64 ep_bitmap);
	u64 (*create_tbl_addr)(bool is_sys, u64 addr);
	void (*parse_tbl_addr)(u64 hwaddr, u64 *addr, bool *is_sys);
	int (*rt_generate_hw_rule)(struct ipahal_rt_rule_gen_params *params,
		u32 *hw_len, u8 *buf);
	int (*flt_generate_hw_rule)(struct ipahal_flt_rule_gen_params *params,
		u32 *hw_len, u8 *buf);
	int (*flt_generate_eq)(enum ipa_ip_type ipt,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb);
	int (*rt_parse_hw_rule)(u8 *addr, struct ipahal_rt_rule_entry *rule);
	int (*flt_parse_hw_rule)(u8 *addr, struct ipahal_flt_rule_entry *rule);
	u8 eq_bitfield[IPA_EQ_MAX];
	u32 prefetech_buf_size;
};


static u64 ipa_fltrt_create_flt_bitmap(u64 ep_bitmap)
{
	/* At IPA3, there global configuration is possible but not used */
	return (ep_bitmap << 1) & ~0x1;
}

static u64 ipa_fltrt_create_tbl_addr(bool is_sys, u64 addr)
{
	if (is_sys) {
		if (addr & IPA3_0_HW_TBL_SYSADDR_ALIGNMENT) {
			IPAHAL_ERR(
				"sys addr is not aligned accordingly addr=0x%pad\n",
				&addr);
			ipa_assert();
			return 0;
		}
	} else {
		if (addr & IPA3_0_HW_TBL_LCLADDR_ALIGNMENT) {
			IPAHAL_ERR("addr/ofst isn't lcl addr aligned %llu\n",
				addr);
			ipa_assert();
			return 0;
		}
		/*
		 * for local tables (at sram) offsets is used as tables
		 * addresses. offset need to be in 8B units
		 * (local address aligned) and left shifted to its place.
		 * Local bit need to be enabled.
		 */
		addr /= IPA3_0_HW_TBL_LCLADDR_ALIGNMENT + 1;
		addr *= IPA3_0_HW_TBL_ADDR_MASK + 1;
		addr += 1;
	}

	return addr;
}

static void ipa_fltrt_parse_tbl_addr(u64 hwaddr, u64 *addr, bool *is_sys)
{
	IPAHAL_DBG_LOW("Parsing hwaddr 0x%llx\n", hwaddr);

	*is_sys = !(hwaddr & 0x1);
	hwaddr &= (~0ULL - 1);
	if (hwaddr & IPA3_0_HW_TBL_SYSADDR_ALIGNMENT) {
		IPAHAL_ERR(
			"sys addr is not aligned accordingly addr=0x%pad\n",
			&hwaddr);
		ipa_assert();
		return;
	}

	if (!*is_sys) {
		hwaddr /= IPA3_0_HW_TBL_ADDR_MASK + 1;
		hwaddr *= IPA3_0_HW_TBL_LCLADDR_ALIGNMENT + 1;
	}

	*addr = hwaddr;
}

/* Update these tables of the number of equations changes */
static const int ipa3_0_ofst_meq32[] = { IPA_OFFSET_MEQ32_0,
					IPA_OFFSET_MEQ32_1};
static const int ipa3_0_ofst_meq128[] = { IPA_OFFSET_MEQ128_0,
					IPA_OFFSET_MEQ128_1};
static const int ipa3_0_ihl_ofst_rng16[] = { IPA_IHL_OFFSET_RANGE16_0,
					IPA_IHL_OFFSET_RANGE16_1};
static const int ipa3_0_ihl_ofst_meq32[] = { IPA_IHL_OFFSET_MEQ32_0,
					IPA_IHL_OFFSET_MEQ32_1};

static int ipa_fltrt_generate_hw_rule_bdy(enum ipa_ip_type ipt,
	const struct ipa_rule_attrib *attrib, u8 **buf, u16 *en_rule);
static int ipa_fltrt_generate_hw_rule_bdy_from_eq(
		const struct ipa_ipfltri_rule_eq *attrib, u8 **buf);
static int ipa_flt_generate_eq_ip4(enum ipa_ip_type ip,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb);
static int ipa_flt_generate_eq_ip6(enum ipa_ip_type ip,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb);
static int ipa_flt_generate_eq(enum ipa_ip_type ipt,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb);
static int ipa_rt_parse_hw_rule(u8 *addr,
		struct ipahal_rt_rule_entry *rule);
static int ipa_rt_parse_hw_rule_ipav4_5(u8 *addr,
		struct ipahal_rt_rule_entry *rule);
static int ipa_flt_parse_hw_rule(u8 *addr,
		struct ipahal_flt_rule_entry *rule);
static int ipa_flt_parse_hw_rule_ipav4(u8 *addr,
		struct ipahal_flt_rule_entry *rule);
static int ipa_flt_parse_hw_rule_ipav4_5(u8 *addr,
	struct ipahal_flt_rule_entry *rule);

#define IPA_IS_RAN_OUT_OF_EQ(__eq_array, __eq_index) \
	(ARRAY_SIZE(__eq_array) <= (__eq_index))

#define IPA_GET_RULE_EQ_BIT_PTRN(__eq) \
	(BIT(ipahal_fltrt_objs[ipahal_ctx->hw_type].eq_bitfield[(__eq)]))

#define IPA_IS_RULE_EQ_VALID(__eq) \
	(ipahal_fltrt_objs[ipahal_ctx->hw_type].eq_bitfield[(__eq)] != 0xFF)

/*
 * ipa_fltrt_rule_generation_err_check() - check basic validity on the rule
 *  attribs before starting building it
 *  checks if not not using ipv4 attribs on ipv6 and vice-versa
 * @ip: IP address type
 * @attrib: IPA rule attribute
 *
 * Return: 0 on success, -EPERM on failure
 */
static int ipa_fltrt_rule_generation_err_check(
	enum ipa_ip_type ipt, const struct ipa_rule_attrib *attrib)
{
	if (ipt == IPA_IP_v4) {
		if (attrib->attrib_mask & IPA_FLT_NEXT_HDR ||
		    attrib->attrib_mask & IPA_FLT_TC ||
		    attrib->attrib_mask & IPA_FLT_FLOW_LABEL) {
			IPAHAL_ERR_RL("v6 attrib's specified for v4 rule\n");
			return -EPERM;
		}
	} else if (ipt == IPA_IP_v6) {
		if (attrib->attrib_mask & IPA_FLT_TOS ||
		    attrib->attrib_mask & IPA_FLT_PROTOCOL) {
			IPAHAL_ERR_RL("v4 attrib's specified for v6 rule\n");
			return -EPERM;
		}
	} else {
		IPAHAL_ERR_RL("unsupported ip %d\n", ipt);
		return -EPERM;
	}

	return 0;
}

static int ipa_rt_gen_hw_rule(struct ipahal_rt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipa3_0_rt_rule_hw_hdr *rule_hdr;
	u8 *start;
	u16 en_rule = 0;

	start = buf;
	rule_hdr = (struct ipa3_0_rt_rule_hw_hdr *)buf;

	ipa_assert_on(params->dst_pipe_idx & ~0x1F);
	rule_hdr->u.hdr.pipe_dest_idx = params->dst_pipe_idx;
	switch (params->hdr_type) {
	case IPAHAL_RT_RULE_HDR_PROC_CTX:
		rule_hdr->u.hdr.system = !params->hdr_lcl;
		rule_hdr->u.hdr.proc_ctx = 1;
		ipa_assert_on(params->hdr_ofst & 31);
		rule_hdr->u.hdr.hdr_offset = (params->hdr_ofst) >> 5;
		break;
	case IPAHAL_RT_RULE_HDR_RAW:
		rule_hdr->u.hdr.system = !params->hdr_lcl;
		rule_hdr->u.hdr.proc_ctx = 0;
		ipa_assert_on(params->hdr_ofst & 3);
		rule_hdr->u.hdr.hdr_offset = (params->hdr_ofst) >> 2;
		break;
	case IPAHAL_RT_RULE_HDR_NONE:
		rule_hdr->u.hdr.system = !params->hdr_lcl;
		rule_hdr->u.hdr.proc_ctx = 0;
		rule_hdr->u.hdr.hdr_offset = 0;
		break;
	default:
		IPAHAL_ERR("Invalid HDR type %d\n", params->hdr_type);
		WARN_ON_RATELIMIT_IPA(1);
		return -EINVAL;
	}

	ipa_assert_on(params->priority & ~0x3FF);
	rule_hdr->u.hdr.priority = params->priority;
	rule_hdr->u.hdr.retain_hdr = params->rule->retain_hdr ? 0x1 : 0x0;
	ipa_assert_on(params->id & ~((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	ipa_assert_on(params->id == ((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	rule_hdr->u.hdr.rule_id = params->id;

	buf += sizeof(struct ipa3_0_rt_rule_hw_hdr);

	if (ipa_fltrt_generate_hw_rule_bdy(params->ipt, &params->rule->attrib,
		&buf, &en_rule)) {
		IPAHAL_ERR("fail to generate hw rule\n");
		return -EPERM;
	}
	rule_hdr->u.hdr.en_rule = en_rule;

	IPAHAL_DBG_LOW("en_rule 0x%x\n", en_rule);
	ipa_write_64(rule_hdr->u.word, (u8 *)rule_hdr);

	if (*hw_len == 0) {
		*hw_len = buf - start;
	} else if (*hw_len != (buf - start)) {
		IPAHAL_ERR("hw_len differs b/w passed=0x%x calc=%td\n",
			*hw_len, (buf - start));
		return -EPERM;
	}

	return 0;
}

static int ipa_rt_gen_hw_rule_ipav4_5(struct ipahal_rt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipa4_5_rt_rule_hw_hdr *rule_hdr;
	u8 *start;
	u16 en_rule = 0;

	start = buf;
	rule_hdr = (struct ipa4_5_rt_rule_hw_hdr *)buf;

	ipa_assert_on(params->dst_pipe_idx & ~0x1F);
	rule_hdr->u.hdr.pipe_dest_idx = params->dst_pipe_idx;
	switch (params->hdr_type) {
	case IPAHAL_RT_RULE_HDR_PROC_CTX:
		rule_hdr->u.hdr.system = !params->hdr_lcl;
		rule_hdr->u.hdr.proc_ctx = 1;
		ipa_assert_on(params->hdr_ofst & 31);
		rule_hdr->u.hdr.hdr_offset = (params->hdr_ofst) >> 5;
		break;
	case IPAHAL_RT_RULE_HDR_RAW:
		rule_hdr->u.hdr.system = !params->hdr_lcl;
		rule_hdr->u.hdr.proc_ctx = 0;
		ipa_assert_on(params->hdr_ofst & 3);
		rule_hdr->u.hdr.hdr_offset = (params->hdr_ofst) >> 2;
		break;
	case IPAHAL_RT_RULE_HDR_NONE:
		rule_hdr->u.hdr.system = !params->hdr_lcl;
		rule_hdr->u.hdr.proc_ctx = 0;
		rule_hdr->u.hdr.hdr_offset = 0;
		break;
	default:
		IPAHAL_ERR("Invalid HDR type %d\n", params->hdr_type);
		WARN_ON_RATELIMIT_IPA(1);
		return -EINVAL;
	}

	ipa_assert_on(params->priority & ~0x3FF);
	rule_hdr->u.hdr.priority = params->priority;
	rule_hdr->u.hdr.retain_hdr = params->rule->retain_hdr ? 0x1 : 0x0;
	ipa_assert_on(params->id & ~((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	ipa_assert_on(params->id == ((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	rule_hdr->u.hdr.rule_id = params->id;
	rule_hdr->u.hdr.stats_cnt_idx_lsb = params->cnt_idx & 0x3F;
	rule_hdr->u.hdr.stats_cnt_idx_msb = (params->cnt_idx & 0xC0) >> 6;

	buf += sizeof(struct ipa4_5_rt_rule_hw_hdr);

	if (ipa_fltrt_generate_hw_rule_bdy(params->ipt, &params->rule->attrib,
		&buf, &en_rule)) {
		IPAHAL_ERR("fail to generate hw rule\n");
		return -EPERM;
	}
	rule_hdr->u.hdr.en_rule = en_rule;

	IPAHAL_DBG_LOW("en_rule 0x%x\n", en_rule);
	ipa_write_64(rule_hdr->u.word, (u8 *)rule_hdr);

	if (*hw_len == 0) {
		*hw_len = buf - start;
	} else if (*hw_len != (buf - start)) {
		IPAHAL_ERR("hw_len differs b/w passed=0x%x calc=%td\n",
			*hw_len, (buf - start));
		return -EPERM;
	}

	return 0;
}

static int ipa_flt_gen_hw_rule(struct ipahal_flt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipa3_0_flt_rule_hw_hdr *rule_hdr;
	u8 *start;
	u16 en_rule = 0;

	start = buf;
	rule_hdr = (struct ipa3_0_flt_rule_hw_hdr *)buf;

	switch (params->rule->action) {
	case IPA_PASS_TO_ROUTING:
		rule_hdr->u.hdr.action = 0x0;
		break;
	case IPA_PASS_TO_SRC_NAT:
		rule_hdr->u.hdr.action = 0x1;
		break;
	case IPA_PASS_TO_DST_NAT:
		rule_hdr->u.hdr.action = 0x2;
		break;
	case IPA_PASS_TO_EXCEPTION:
		rule_hdr->u.hdr.action = 0x3;
		break;
	default:
		IPAHAL_ERR_RL("Invalid Rule Action %d\n", params->rule->action);
		WARN_ON_RATELIMIT_IPA(1);
		return -EINVAL;
	}
	ipa_assert_on(params->rt_tbl_idx & ~0x1F);
	rule_hdr->u.hdr.rt_tbl_idx = params->rt_tbl_idx;
	rule_hdr->u.hdr.retain_hdr = params->rule->retain_hdr ? 0x1 : 0x0;
	rule_hdr->u.hdr.rsvd1 = 0;
	rule_hdr->u.hdr.rsvd2 = 0;
	rule_hdr->u.hdr.rsvd3 = 0;

	ipa_assert_on(params->priority & ~0x3FF);
	rule_hdr->u.hdr.priority = params->priority;
	ipa_assert_on(params->id & ~((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	ipa_assert_on(params->id == ((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	rule_hdr->u.hdr.rule_id = params->id;

	buf += sizeof(struct ipa3_0_flt_rule_hw_hdr);

	if (params->rule->eq_attrib_type) {
		if (ipa_fltrt_generate_hw_rule_bdy_from_eq(
			&params->rule->eq_attrib, &buf)) {
			IPAHAL_ERR_RL("fail to generate hw rule from eq\n");
			return -EPERM;
		}
		en_rule = params->rule->eq_attrib.rule_eq_bitmap;
	} else {
		if (ipa_fltrt_generate_hw_rule_bdy(params->ipt,
			&params->rule->attrib, &buf, &en_rule)) {
			IPAHAL_ERR_RL("fail to generate hw rule\n");
			return -EPERM;
		}
	}
	rule_hdr->u.hdr.en_rule = en_rule;

	IPAHAL_DBG_LOW("en_rule=0x%x, action=%d, rt_idx=%d, retain_hdr=%d\n",
		en_rule,
		rule_hdr->u.hdr.action,
		rule_hdr->u.hdr.rt_tbl_idx,
		rule_hdr->u.hdr.retain_hdr);
	IPAHAL_DBG_LOW("priority=%d, rule_id=%d\n",
		rule_hdr->u.hdr.priority,
		rule_hdr->u.hdr.rule_id);

	ipa_write_64(rule_hdr->u.word, (u8 *)rule_hdr);

	if (*hw_len == 0) {
		*hw_len = buf - start;
	} else if (*hw_len != (buf - start)) {
		IPAHAL_ERR_RL("hw_len differs b/w passed=0x%x calc=%td\n",
			*hw_len, (buf - start));
		return -EPERM;
	}

	return 0;
}

static int ipa_flt_gen_hw_rule_ipav4(struct ipahal_flt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipa4_0_flt_rule_hw_hdr *rule_hdr;
	u8 *start;
	u16 en_rule = 0;

	start = buf;
	rule_hdr = (struct ipa4_0_flt_rule_hw_hdr *)buf;

	switch (params->rule->action) {
	case IPA_PASS_TO_ROUTING:
		rule_hdr->u.hdr.action = 0x0;
		break;
	case IPA_PASS_TO_SRC_NAT:
		rule_hdr->u.hdr.action = 0x1;
		break;
	case IPA_PASS_TO_DST_NAT:
		rule_hdr->u.hdr.action = 0x2;
		break;
	case IPA_PASS_TO_EXCEPTION:
		rule_hdr->u.hdr.action = 0x3;
		break;
	default:
		IPAHAL_ERR("Invalid Rule Action %d\n", params->rule->action);
		WARN_ON_RATELIMIT_IPA(1);
		return -EINVAL;
	}

	ipa_assert_on(params->rt_tbl_idx & ~0x1F);
	rule_hdr->u.hdr.rt_tbl_idx = params->rt_tbl_idx;
	rule_hdr->u.hdr.retain_hdr = params->rule->retain_hdr ? 0x1 : 0x0;

	ipa_assert_on(params->rule->pdn_idx & ~0xF);
	rule_hdr->u.hdr.pdn_idx = params->rule->pdn_idx;
	rule_hdr->u.hdr.set_metadata = params->rule->set_metadata;
	rule_hdr->u.hdr.rsvd2 = 0;
	rule_hdr->u.hdr.rsvd3 = 0;

	ipa_assert_on(params->priority & ~0x3FF);
	rule_hdr->u.hdr.priority = params->priority;
	ipa_assert_on(params->id & ~((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	ipa_assert_on(params->id == ((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	rule_hdr->u.hdr.rule_id = params->id;

	buf += sizeof(struct ipa4_0_flt_rule_hw_hdr);

	if (params->rule->eq_attrib_type) {
		if (ipa_fltrt_generate_hw_rule_bdy_from_eq(
			&params->rule->eq_attrib, &buf)) {
			IPAHAL_ERR("fail to generate hw rule from eq\n");
			return -EPERM;
		}
		en_rule = params->rule->eq_attrib.rule_eq_bitmap;
	} else {
		if (ipa_fltrt_generate_hw_rule_bdy(params->ipt,
			&params->rule->attrib, &buf, &en_rule)) {
			IPAHAL_ERR("fail to generate hw rule\n");
			return -EPERM;
		}
	}
	rule_hdr->u.hdr.en_rule = en_rule;

	IPAHAL_DBG_LOW("en_rule=0x%x, action=%d, rt_idx=%d, retain_hdr=%d\n",
		en_rule,
		rule_hdr->u.hdr.action,
		rule_hdr->u.hdr.rt_tbl_idx,
		rule_hdr->u.hdr.retain_hdr);
	IPAHAL_DBG_LOW("priority=%d, rule_id=%d, pdn=%d, set_metadata=%d\n",
		rule_hdr->u.hdr.priority,
		rule_hdr->u.hdr.rule_id,
		rule_hdr->u.hdr.pdn_idx,
		rule_hdr->u.hdr.set_metadata);

	ipa_write_64(rule_hdr->u.word, (u8 *)rule_hdr);

	if (*hw_len == 0) {
		*hw_len = buf - start;
	} else if (*hw_len != (buf - start)) {
		IPAHAL_ERR("hw_len differs b/w passed=0x%x calc=%td\n",
			*hw_len, (buf - start));
		return -EPERM;
	}

	return 0;
}

static int ipa_flt_gen_hw_rule_ipav4_5(
	struct ipahal_flt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipa4_5_flt_rule_hw_hdr *rule_hdr;
	u8 *start;
	u16 en_rule = 0;

	start = buf;
	rule_hdr = (struct ipa4_5_flt_rule_hw_hdr *)buf;

	switch (params->rule->action) {
	case IPA_PASS_TO_ROUTING:
		rule_hdr->u.hdr.action = 0x0;
		break;
	case IPA_PASS_TO_SRC_NAT:
		rule_hdr->u.hdr.action = 0x1;
		break;
	case IPA_PASS_TO_DST_NAT:
		rule_hdr->u.hdr.action = 0x2;
		break;
	case IPA_PASS_TO_EXCEPTION:
		rule_hdr->u.hdr.action = 0x3;
		break;
	default:
		IPAHAL_ERR("Invalid Rule Action %d\n", params->rule->action);
		WARN_ON_RATELIMIT_IPA(1);
		return -EINVAL;
	}

	ipa_assert_on(params->rt_tbl_idx & ~0x1F);
	rule_hdr->u.hdr.rt_tbl_idx = params->rt_tbl_idx;
	rule_hdr->u.hdr.retain_hdr = params->rule->retain_hdr ? 0x1 : 0x0;

	ipa_assert_on(params->rule->pdn_idx & ~0xF);
	rule_hdr->u.hdr.pdn_idx = params->rule->pdn_idx;
	rule_hdr->u.hdr.set_metadata = params->rule->set_metadata;
	rule_hdr->u.hdr.rsvd2 = 0;

	ipa_assert_on(params->priority & ~0x3FF);
	rule_hdr->u.hdr.priority = params->priority;
	ipa_assert_on(params->id & ~((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	ipa_assert_on(params->id == ((1 << IPA3_0_RULE_ID_BIT_LEN) - 1));
	rule_hdr->u.hdr.rule_id = params->id;
	rule_hdr->u.hdr.stats_cnt_idx_lsb = params->cnt_idx & 0x3F;
	rule_hdr->u.hdr.stats_cnt_idx_msb = (params->cnt_idx & 0xC0) >> 6;

	buf += sizeof(struct ipa4_5_flt_rule_hw_hdr);

	if (params->rule->eq_attrib_type) {
		if (ipa_fltrt_generate_hw_rule_bdy_from_eq(
			&params->rule->eq_attrib, &buf)) {
			IPAHAL_ERR("fail to generate hw rule from eq\n");
			return -EPERM;
		}
		en_rule = params->rule->eq_attrib.rule_eq_bitmap;
	} else {
		if (ipa_fltrt_generate_hw_rule_bdy(params->ipt,
			&params->rule->attrib, &buf, &en_rule)) {
			IPAHAL_ERR("fail to generate hw rule\n");
			return -EPERM;
		}
	}
	rule_hdr->u.hdr.en_rule = en_rule;

	IPAHAL_DBG_LOW("en_rule=0x%x, action=%d, rt_idx=%d, retain_hdr=%d\n",
		en_rule,
		rule_hdr->u.hdr.action,
		rule_hdr->u.hdr.rt_tbl_idx,
		rule_hdr->u.hdr.retain_hdr);
	IPAHAL_DBG_LOW("priority=%d, rule_id=%d, pdn=%d, set_metadata=%d\n",
		rule_hdr->u.hdr.priority,
		rule_hdr->u.hdr.rule_id,
		rule_hdr->u.hdr.pdn_idx,
		rule_hdr->u.hdr.set_metadata);

	ipa_write_64(rule_hdr->u.word, (u8 *)rule_hdr);

	if (*hw_len == 0) {
		*hw_len = buf - start;
	} else if (*hw_len != (buf - start)) {
		IPAHAL_ERR("hw_len differs b/w passed=0x%x calc=%td\n",
			*hw_len, (buf - start));
		return -EPERM;
	}

	return 0;
}

/*
 * This array contains the FLT/RT info for IPAv3 and later.
 * All the information on IPAv3 are statically defined below.
 * If information is missing regarding on some IPA version,
 *  the init function will fill it with the information from the previous
 *  IPA version.
 * Information is considered missing if all of the fields are 0.
 */
static struct ipahal_fltrt_obj ipahal_fltrt_objs[IPA_HW_MAX] = {
	/* IPAv3 */
	[IPA_HW_v3_0] = {
		true,
		IPA3_0_HW_TBL_WIDTH,
		IPA3_0_HW_TBL_SYSADDR_ALIGNMENT,
		IPA3_0_HW_TBL_LCLADDR_ALIGNMENT,
		IPA3_0_HW_TBL_BLK_SIZE_ALIGNMENT,
		IPA3_0_HW_RULE_START_ALIGNMENT,
		IPA3_0_HW_TBL_HDR_WIDTH,
		IPA3_0_HW_TBL_ADDR_MASK,
		IPA3_0_RULE_MAX_PRIORITY,
		IPA3_0_RULE_MIN_PRIORITY,
		IPA3_0_LOW_RULE_ID,
		IPA3_0_RULE_ID_BIT_LEN,
		IPA3_0_HW_RULE_BUF_SIZE,
		ipa_write_64,
		ipa_fltrt_create_flt_bitmap,
		ipa_fltrt_create_tbl_addr,
		ipa_fltrt_parse_tbl_addr,
		ipa_rt_gen_hw_rule,
		ipa_flt_gen_hw_rule,
		ipa_flt_generate_eq,
		ipa_rt_parse_hw_rule,
		ipa_flt_parse_hw_rule,
		{
			[IPA_TOS_EQ]			= 0,
			[IPA_PROTOCOL_EQ]		= 1,
			[IPA_TC_EQ]			= 2,
			[IPA_OFFSET_MEQ128_0]		= 3,
			[IPA_OFFSET_MEQ128_1]		= 4,
			[IPA_OFFSET_MEQ32_0]		= 5,
			[IPA_OFFSET_MEQ32_1]		= 6,
			[IPA_IHL_OFFSET_MEQ32_0]	= 7,
			[IPA_IHL_OFFSET_MEQ32_1]	= 8,
			[IPA_METADATA_COMPARE]		= 9,
			[IPA_IHL_OFFSET_RANGE16_0]	= 10,
			[IPA_IHL_OFFSET_RANGE16_1]	= 11,
			[IPA_IHL_OFFSET_EQ_32]		= 12,
			[IPA_IHL_OFFSET_EQ_16]		= 13,
			[IPA_FL_EQ]			= 14,
			[IPA_IS_FRAG]			= 15,
			[IPA_IS_PURE_ACK]		= 0xFF,
		},
		IPA3_0_HW_RULE_PREFETCH_BUF_SIZE,
	},

	/* IPAv4 */
	[IPA_HW_v4_0] = {
		true,
		IPA3_0_HW_TBL_WIDTH,
		IPA3_0_HW_TBL_SYSADDR_ALIGNMENT,
		IPA3_0_HW_TBL_LCLADDR_ALIGNMENT,
		IPA3_0_HW_TBL_BLK_SIZE_ALIGNMENT,
		IPA3_0_HW_RULE_START_ALIGNMENT,
		IPA3_0_HW_TBL_HDR_WIDTH,
		IPA3_0_HW_TBL_ADDR_MASK,
		IPA3_0_RULE_MAX_PRIORITY,
		IPA3_0_RULE_MIN_PRIORITY,
		IPA3_0_LOW_RULE_ID,
		IPA3_0_RULE_ID_BIT_LEN,
		IPA3_0_HW_RULE_BUF_SIZE,
		ipa_write_64,
		ipa_fltrt_create_flt_bitmap,
		ipa_fltrt_create_tbl_addr,
		ipa_fltrt_parse_tbl_addr,
		ipa_rt_gen_hw_rule,
		ipa_flt_gen_hw_rule_ipav4,
		ipa_flt_generate_eq,
		ipa_rt_parse_hw_rule,
		ipa_flt_parse_hw_rule_ipav4,
		{
			[IPA_TOS_EQ]			= 0,
			[IPA_PROTOCOL_EQ]		= 1,
			[IPA_TC_EQ]			= 2,
			[IPA_OFFSET_MEQ128_0]		= 3,
			[IPA_OFFSET_MEQ128_1]		= 4,
			[IPA_OFFSET_MEQ32_0]		= 5,
			[IPA_OFFSET_MEQ32_1]		= 6,
			[IPA_IHL_OFFSET_MEQ32_0]	= 7,
			[IPA_IHL_OFFSET_MEQ32_1]	= 8,
			[IPA_METADATA_COMPARE]		= 9,
			[IPA_IHL_OFFSET_RANGE16_0]	= 10,
			[IPA_IHL_OFFSET_RANGE16_1]	= 11,
			[IPA_IHL_OFFSET_EQ_32]		= 12,
			[IPA_IHL_OFFSET_EQ_16]		= 13,
			[IPA_FL_EQ]			= 14,
			[IPA_IS_FRAG]			= 15,
			[IPA_IS_PURE_ACK]		= 0xFF,
		},
		IPA3_0_HW_RULE_PREFETCH_BUF_SIZE,
	},

	/* IPAv4.2 */
	[IPA_HW_v4_2] = {
		false,
		IPA3_0_HW_TBL_WIDTH,
		IPA3_0_HW_TBL_SYSADDR_ALIGNMENT,
		IPA3_0_HW_TBL_LCLADDR_ALIGNMENT,
		IPA3_0_HW_TBL_BLK_SIZE_ALIGNMENT,
		IPA3_0_HW_RULE_START_ALIGNMENT,
		IPA3_0_HW_TBL_HDR_WIDTH,
		IPA3_0_HW_TBL_ADDR_MASK,
		IPA3_0_RULE_MAX_PRIORITY,
		IPA3_0_RULE_MIN_PRIORITY,
		IPA3_0_LOW_RULE_ID,
		IPA3_0_RULE_ID_BIT_LEN,
		IPA3_0_HW_RULE_BUF_SIZE,
		ipa_write_64,
		ipa_fltrt_create_flt_bitmap,
		ipa_fltrt_create_tbl_addr,
		ipa_fltrt_parse_tbl_addr,
		ipa_rt_gen_hw_rule,
		ipa_flt_gen_hw_rule_ipav4,
		ipa_flt_generate_eq,
		ipa_rt_parse_hw_rule,
		ipa_flt_parse_hw_rule_ipav4,
		{
			[IPA_TOS_EQ]			= 0,
			[IPA_PROTOCOL_EQ]		= 1,
			[IPA_TC_EQ]			= 2,
			[IPA_OFFSET_MEQ128_0]		= 3,
			[IPA_OFFSET_MEQ128_1]		= 4,
			[IPA_OFFSET_MEQ32_0]		= 5,
			[IPA_OFFSET_MEQ32_1]		= 6,
			[IPA_IHL_OFFSET_MEQ32_0]	= 7,
			[IPA_IHL_OFFSET_MEQ32_1]	= 8,
			[IPA_METADATA_COMPARE]		= 9,
			[IPA_IHL_OFFSET_RANGE16_0]	= 10,
			[IPA_IHL_OFFSET_RANGE16_1]	= 11,
			[IPA_IHL_OFFSET_EQ_32]		= 12,
			[IPA_IHL_OFFSET_EQ_16]		= 13,
			[IPA_FL_EQ]			= 14,
			[IPA_IS_FRAG]			= 15,
			[IPA_IS_PURE_ACK]		= 0xFF,
		},
		IPA3_0_HW_RULE_PREFETCH_BUF_SIZE,
	},

	/* IPAv4.5 */
	[IPA_HW_v4_5] = {
		true,
		IPA3_0_HW_TBL_WIDTH,
		IPA3_0_HW_TBL_SYSADDR_ALIGNMENT,
		IPA3_0_HW_TBL_LCLADDR_ALIGNMENT,
		IPA3_0_HW_TBL_BLK_SIZE_ALIGNMENT,
		IPA3_0_HW_RULE_START_ALIGNMENT,
		IPA3_0_HW_TBL_HDR_WIDTH,
		IPA3_0_HW_TBL_ADDR_MASK,
		IPA3_0_RULE_MAX_PRIORITY,
		IPA3_0_RULE_MIN_PRIORITY,
		IPA3_0_LOW_RULE_ID,
		IPA3_0_RULE_ID_BIT_LEN,
		IPA3_0_HW_RULE_BUF_SIZE,
		ipa_write_64,
		ipa_fltrt_create_flt_bitmap,
		ipa_fltrt_create_tbl_addr,
		ipa_fltrt_parse_tbl_addr,
		ipa_rt_gen_hw_rule_ipav4_5,
		ipa_flt_gen_hw_rule_ipav4_5,
		ipa_flt_generate_eq,
		ipa_rt_parse_hw_rule_ipav4_5,
		ipa_flt_parse_hw_rule_ipav4_5,
		{
			[IPA_TOS_EQ]			= 0xFF,
			[IPA_PROTOCOL_EQ]		= 1,
			[IPA_TC_EQ]			= 2,
			[IPA_OFFSET_MEQ128_0]		= 3,
			[IPA_OFFSET_MEQ128_1]		= 4,
			[IPA_OFFSET_MEQ32_0]		= 5,
			[IPA_OFFSET_MEQ32_1]		= 6,
			[IPA_IHL_OFFSET_MEQ32_0]	= 7,
			[IPA_IHL_OFFSET_MEQ32_1]	= 8,
			[IPA_METADATA_COMPARE]		= 9,
			[IPA_IHL_OFFSET_RANGE16_0]	= 10,
			[IPA_IHL_OFFSET_RANGE16_1]	= 11,
			[IPA_IHL_OFFSET_EQ_32]		= 12,
			[IPA_IHL_OFFSET_EQ_16]		= 13,
			[IPA_FL_EQ]			= 14,
			[IPA_IS_FRAG]			= 15,
			[IPA_IS_PURE_ACK]		= 0,
		},
		IPA3_0_HW_RULE_PREFETCH_BUF_SIZE,
	},
};

static int ipa_flt_generate_eq(enum ipa_ip_type ipt,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb)
{
	if (ipa_fltrt_rule_generation_err_check(ipt, attrib))
		return -EPERM;

	if (ipt == IPA_IP_v4) {
		if (ipa_flt_generate_eq_ip4(ipt, attrib, eq_atrb)) {
			IPAHAL_ERR("failed to build ipv4 flt eq rule\n");
			return -EPERM;
		}
	} else if (ipt == IPA_IP_v6) {
		if (ipa_flt_generate_eq_ip6(ipt, attrib, eq_atrb)) {
			IPAHAL_ERR("failed to build ipv6 flt eq rule\n");
			return -EPERM;
		}
	} else {
		IPAHAL_ERR("unsupported ip %d\n", ipt);
		return  -EPERM;
	}

	/*
	 * default "rule" means no attributes set -> map to
	 * OFFSET_MEQ32_0 with mask of 0 and val of 0 and offset 0
	 */
	if (attrib->attrib_mask == 0) {
		eq_atrb->rule_eq_bitmap = 0;
		eq_atrb->rule_eq_bitmap |= IPA_GET_RULE_EQ_BIT_PTRN(
			IPA_OFFSET_MEQ32_0);
		eq_atrb->offset_meq_32[0].offset = 0;
		eq_atrb->offset_meq_32[0].mask = 0;
		eq_atrb->offset_meq_32[0].value = 0;
	}

	return 0;
}

static void ipa_fltrt_generate_mac_addr_hw_rule(u8 **extra, u8 **rest,
	u8 hdr_mac_addr_offset,
	const uint8_t mac_addr_mask[ETH_ALEN],
	const uint8_t mac_addr[ETH_ALEN])
{
	int i;

	*extra = ipa_write_8(hdr_mac_addr_offset, *extra);

	/* LSB MASK and ADDR */
	*rest = ipa_write_64(0, *rest);
	*rest = ipa_write_64(0, *rest);

	/* MSB MASK and ADDR */
	*rest = ipa_write_16(0, *rest);
	for (i = 5; i >= 0; i--)
		*rest = ipa_write_8(mac_addr_mask[i], *rest);
	*rest = ipa_write_16(0, *rest);
	for (i = 5; i >= 0; i--)
		*rest = ipa_write_8(mac_addr[i], *rest);
}

static inline void ipa_fltrt_get_mac_data(const struct ipa_rule_attrib *attrib,
	uint32_t attrib_mask, u8 *offset, const uint8_t **mac_addr,
	const uint8_t **mac_addr_mask)
{
	if (attrib_mask & IPA_FLT_MAC_DST_ADDR_ETHER_II) {
		*offset = -14;
		*mac_addr = attrib->dst_mac_addr;
		*mac_addr_mask = attrib->dst_mac_addr_mask;
		return;
	}

	if (attrib_mask & IPA_FLT_MAC_SRC_ADDR_ETHER_II) {
		*offset = -8;
		*mac_addr = attrib->src_mac_addr;
		*mac_addr_mask = attrib->src_mac_addr_mask;
		return;
	}

	if (attrib_mask & IPA_FLT_MAC_DST_ADDR_802_3) {
		*offset = -22;
		*mac_addr = attrib->dst_mac_addr;
		*mac_addr_mask = attrib->dst_mac_addr_mask;
		return;
	}

	if (attrib_mask & IPA_FLT_MAC_SRC_ADDR_802_3) {
		*offset = -16;
		*mac_addr = attrib->src_mac_addr;
		*mac_addr_mask = attrib->src_mac_addr_mask;
		return;
	}

	if (attrib_mask & IPA_FLT_MAC_DST_ADDR_802_1Q) {
		*offset = -18;
		*mac_addr = attrib->dst_mac_addr;
		*mac_addr_mask = attrib->dst_mac_addr_mask;
		return;
	}

	if (attrib_mask & IPA_FLT_MAC_SRC_ADDR_802_1Q) {
		*offset = -10;
		*mac_addr = attrib->src_mac_addr;
		*mac_addr_mask = attrib->src_mac_addr_mask;
		return;
	}
}

static int ipa_fltrt_generate_mac_hw_rule_bdy(u16 *en_rule,
	const struct ipa_rule_attrib *attrib,
	u8 *ofst_meq128, u8 **extra, u8 **rest)
{
	u8 offset = 0;
	const uint8_t *mac_addr = NULL;
	const uint8_t *mac_addr_mask = NULL;
	int i;
	uint32_t attrib_mask;

	for (i = 0; i < hweight_long(IPA_MAC_FLT_BITS); i++) {
		switch (i) {
		case 0:
			attrib_mask = IPA_FLT_MAC_DST_ADDR_ETHER_II;
			break;
		case 1:
			attrib_mask = IPA_FLT_MAC_SRC_ADDR_ETHER_II;
			break;
		case 2:
			attrib_mask = IPA_FLT_MAC_DST_ADDR_802_3;
			break;
		case 3:
			attrib_mask = IPA_FLT_MAC_SRC_ADDR_802_3;
			break;
		case 4:
			attrib_mask = IPA_FLT_MAC_DST_ADDR_802_1Q;
			break;
		case 5:
			attrib_mask = IPA_FLT_MAC_SRC_ADDR_802_1Q;
			break;
		default:
			return -EPERM;
		}

		attrib_mask &= attrib->attrib_mask;
		if (!attrib_mask)
			continue;

		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, *ofst_meq128)) {
			IPAHAL_ERR("ran out of meq128 eq\n");
			return -EPERM;
		}

		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[*ofst_meq128]);

		ipa_fltrt_get_mac_data(attrib, attrib_mask, &offset,
			&mac_addr, &mac_addr_mask);

		ipa_fltrt_generate_mac_addr_hw_rule(extra, rest, offset,
			mac_addr_mask,
			mac_addr);

		(*ofst_meq128)++;
	}

	return 0;
}

static inline int ipa_fltrt_generate_vlan_hw_rule_bdy(u16 *en_rule,
	const struct ipa_rule_attrib *attrib,
	u8 *ofst_meq32, u8 **extra, u8 **rest)
{
	if (attrib->attrib_mask & IPA_FLT_VLAN_ID) {
		uint32_t vlan_tag;

		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, *ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[*ofst_meq32]);
		/* -6 => offset of 802_1Q tag in L2 hdr */
		*extra = ipa_write_8((u8)-6, *extra);
		/* filter vlan packets: 0x8100 TPID + required VLAN ID */
		vlan_tag = (0x8100 << 16) | (attrib->vlan_id & 0xFFF);
		*rest = ipa_write_32(0xFFFF0FFF, *rest);
		*rest = ipa_write_32(vlan_tag, *rest);
		(*ofst_meq32)++;
	}

	return 0;
}

static int ipa_fltrt_generate_hw_rule_bdy_ip4(u16 *en_rule,
	const struct ipa_rule_attrib *attrib,
	u8 **extra_wrds, u8 **rest_wrds)
{
	u8 *extra = *extra_wrds;
	u8 *rest = *rest_wrds;
	u8 ofst_meq32 = 0;
	u8 ihl_ofst_rng16 = 0;
	u8 ihl_ofst_meq32 = 0;
	u8 ofst_meq128 = 0;
	int rc = 0;
	bool tos_done = false;

	if (attrib->attrib_mask & IPA_FLT_IS_PURE_ACK) {
		if (!IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK)) {
			IPAHAL_ERR("is_pure_ack eq not supported\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_PURE_ACK);
		extra = ipa_write_8(0, extra);
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		if (!IPA_IS_RULE_EQ_VALID(IPA_TOS_EQ)) {
			IPAHAL_DBG("tos eq not supported\n");
		} else {
			*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_TOS_EQ);
			extra = ipa_write_8(attrib->u.v4.tos, extra);
			tos_done = true;
		}
	}

	if (attrib->attrib_mask & IPA_FLT_PROTOCOL) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_PROTOCOL_EQ);
		extra = ipa_write_8(attrib->u.v4.protocol, extra);
	}

	if (attrib->attrib_mask & IPA_MAC_FLT_BITS) {
		if (ipa_fltrt_generate_mac_hw_rule_bdy(en_rule, attrib,
			&ofst_meq128, &extra, &rest))
			goto err;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS_MASKED) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		/* 0 => Take the first word. offset of TOS in v4 header is 1 */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_32((attrib->tos_mask << 16), rest);
		rest = ipa_write_32((attrib->tos_value << 16), rest);
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		/* 12 => offset of src ip in v4 header */
		extra = ipa_write_8(12, extra);
		rest = ipa_write_32(attrib->u.v4.src_addr_mask, rest);
		rest = ipa_write_32(attrib->u.v4.src_addr, rest);
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		/* 16 => offset of dst ip in v4 header */
		extra = ipa_write_8(16, extra);
		rest = ipa_write_32(attrib->u.v4.dst_addr_mask, rest);
		rest = ipa_write_32(attrib->u.v4.dst_addr, rest);
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_ETHER_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		/* -2 => offset of ether type in L2 hdr */
		extra = ipa_write_8((u8)-2, extra);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_16(htons(attrib->ether_type), rest);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_16(htons(attrib->ether_type), rest);
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_DBG("ran out of meq32 eq\n");
		} else {
			*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
				ipa3_0_ofst_meq32[ofst_meq32]);
			/*
			 * 0 => Take the first word.
			 * offset of TOS in v4 header is 1
			 */
			extra = ipa_write_8(0, extra);
			rest = ipa_write_32(0xFF << 16, rest);
			rest = ipa_write_32((attrib->u.v4.tos << 16), rest);
			ofst_meq32++;
			tos_done = true;
		}
	}

	if (ipa_fltrt_generate_vlan_hw_rule_bdy(en_rule, attrib, &ofst_meq32,
		&extra, &rest))
		goto err;

	if (attrib->attrib_mask & IPA_FLT_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 0  => offset of type after v4 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_32(0xFF, rest);
		rest = ipa_write_32(attrib->type, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_CODE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 1  => offset of code after v4 header */
		extra = ipa_write_8(1, extra);
		rest = ipa_write_32(0xFF, rest);
		rest = ipa_write_32(attrib->code, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_SPI) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 0  => offset of SPI after v4 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_32(0xFFFFFFFF, rest);
		rest = ipa_write_32(attrib->spi, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_DST_ADDR_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32) || IPA_IS_RAN_OUT_OF_EQ(
			ipa3_0_ihl_ofst_meq32, ihl_ofst_meq32 + 1)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32 + 1]);
		/* populate first ihl meq eq */
		extra = ipa_write_8(8, extra);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[3], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[2], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[1], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[0], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[3], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[2], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[1], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[0], rest);
		/* populate second ihl meq eq */
		extra = ipa_write_8(12, extra);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[5], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[4], rest);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_8(attrib->dst_mac_addr[5], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[4], rest);
		ihl_ofst_meq32 += 2;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 12  => offset of SYN after v4 header */
		extra = ipa_write_8(12, extra);
		rest = ipa_write_32(0x20000, rest);
		rest = ipa_write_32(0x20000, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_DBG("ran out of ihl_meq32 eq\n");
		} else {
			*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
				ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
			/*
			 * 0 => Take the first word. offset of TOS in
			 * v4 header is 1. MSB bit asserted at IHL means
			 * to ignore packet IHL and do offset inside IPA header
			 */
			extra = ipa_write_8(0x80, extra);
			rest = ipa_write_32(0xFF << 16, rest);
			rest = ipa_write_32((attrib->u.v4.tos << 16), rest);
			ihl_ofst_meq32++;
			tos_done = true;
		}
	}

	if (attrib->attrib_mask & IPA_FLT_META_DATA) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_METADATA_COMPARE);
		rest = ipa_write_32(attrib->meta_data_mask, rest);
		rest = ipa_write_32(attrib->meta_data, rest);
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		if (attrib->src_port_hi < attrib->src_port_lo) {
			IPAHAL_ERR("bad src port range param\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 0  => offset of src port after v4 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_16(attrib->src_port_hi, rest);
		rest = ipa_write_16(attrib->src_port_lo, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		if (attrib->dst_port_hi < attrib->dst_port_lo) {
			IPAHAL_ERR("bad dst port range param\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 2  => offset of dst port after v4 header */
		extra = ipa_write_8(2, extra);
		rest = ipa_write_16(attrib->dst_port_hi, rest);
		rest = ipa_write_16(attrib->dst_port_lo, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 0  => offset of src port after v4 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_16(attrib->src_port, rest);
		rest = ipa_write_16(attrib->src_port, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 2  => offset of dst port after v4 header */
		extra = ipa_write_8(2, extra);
		rest = ipa_write_16(attrib->dst_port, rest);
		rest = ipa_write_16(attrib->dst_port, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_FRAGMENT)
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_FRAG);

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		IPAHAL_ERR("could not find equation for tos\n");
		goto err;
	}

	goto done;

err:
	rc = -EPERM;
done:
	*extra_wrds = extra;
	*rest_wrds = rest;
	return rc;
}

static int ipa_fltrt_generate_hw_rule_bdy_ip6(u16 *en_rule,
	const struct ipa_rule_attrib *attrib,
	u8 **extra_wrds, u8 **rest_wrds)
{
	u8 *extra = *extra_wrds;
	u8 *rest = *rest_wrds;
	u8 ofst_meq32 = 0;
	u8 ihl_ofst_rng16 = 0;
	u8 ihl_ofst_meq32 = 0;
	u8 ofst_meq128 = 0;
	int rc = 0;

	/* v6 code below assumes no extension headers TODO: fix this */
	if (attrib->attrib_mask & IPA_FLT_IS_PURE_ACK) {
		if (!IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK)) {
			IPAHAL_ERR("is_pure_ack eq not supported\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_PURE_ACK);
		extra = ipa_write_8(0, extra);
	}

	if (attrib->attrib_mask & IPA_FLT_NEXT_HDR) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_PROTOCOL_EQ);
		extra = ipa_write_8(attrib->u.v6.next_hdr, extra);
	}

	if (attrib->attrib_mask & IPA_FLT_TC) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_TC_EQ);
		extra = ipa_write_8(attrib->u.v6.tc, extra);
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, ofst_meq128)) {
			IPAHAL_ERR("ran out of meq128 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[ofst_meq128]);
		/* 8 => offset of src ip in v6 header */
		extra = ipa_write_8(8, extra);
		rest = ipa_write_32(attrib->u.v6.src_addr_mask[3], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr_mask[2], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr[3], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr[2], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr_mask[1], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr_mask[0], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr[1], rest);
		rest = ipa_write_32(attrib->u.v6.src_addr[0], rest);
		ofst_meq128++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, ofst_meq128)) {
			IPAHAL_ERR("ran out of meq128 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[ofst_meq128]);
		/* 24 => offset of dst ip in v6 header */
		extra = ipa_write_8(24, extra);
		rest = ipa_write_32(attrib->u.v6.dst_addr_mask[3], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr_mask[2], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr[3], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr[2], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr_mask[1], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr_mask[0], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr[1], rest);
		rest = ipa_write_32(attrib->u.v6.dst_addr[0], rest);
		ofst_meq128++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS_MASKED) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, ofst_meq128)) {
			IPAHAL_ERR("ran out of meq128 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[ofst_meq128]);
		/* 0 => offset of TOS in v6 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_64(0, rest);
		rest = ipa_write_64(0, rest);
		rest = ipa_write_32(0, rest);
		rest = ipa_write_32((attrib->tos_mask << 20), rest);
		rest = ipa_write_32(0, rest);
		rest = ipa_write_32((attrib->tos_value << 20), rest);
		ofst_meq128++;
	}

	if (attrib->attrib_mask & IPA_MAC_FLT_BITS) {
		if (ipa_fltrt_generate_mac_hw_rule_bdy(en_rule, attrib,
			&ofst_meq128, &extra, &rest))
			goto err;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_ETHER_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		/* -2 => offset of ether type in L2 hdr */
		extra = ipa_write_8((u8)-2, extra);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_16(htons(attrib->ether_type), rest);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_16(htons(attrib->ether_type), rest);
		ofst_meq32++;
	}

	if (ipa_fltrt_generate_vlan_hw_rule_bdy(en_rule, attrib, &ofst_meq32,
		&extra, &rest))
		goto err;

	if (attrib->attrib_mask & IPA_FLT_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 0  => offset of type after v6 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_32(0xFF, rest);
		rest = ipa_write_32(attrib->type, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_CODE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 1  => offset of code after v6 header */
		extra = ipa_write_8(1, extra);
		rest = ipa_write_32(0xFF, rest);
		rest = ipa_write_32(attrib->code, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_SPI) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 0  => offset of SPI after v6 header FIXME */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_32(0xFFFFFFFF, rest);
		rest = ipa_write_32(attrib->spi, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_DST_ADDR_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32) || IPA_IS_RAN_OUT_OF_EQ(
			ipa3_0_ihl_ofst_meq32, ihl_ofst_meq32 + 1)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32 + 1]);
		/* populate first ihl meq eq */
		extra = ipa_write_8(8, extra);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[3], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[2], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[1], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[0], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[3], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[2], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[1], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[0], rest);
		/* populate second ihl meq eq */
		extra = ipa_write_8(12, extra);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[5], rest);
		rest = ipa_write_8(attrib->dst_mac_addr_mask[4], rest);
		rest = ipa_write_16(0, rest);
		rest = ipa_write_8(attrib->dst_mac_addr[5], rest);
		rest = ipa_write_8(attrib->dst_mac_addr[4], rest);
		ihl_ofst_meq32 += 2;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 12  => offset of SYN after v4 header */
		extra = ipa_write_8(12, extra);
		rest = ipa_write_32(0x20000, rest);
		rest = ipa_write_32(0x20000, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32) || IPA_IS_RAN_OUT_OF_EQ(
			ipa3_0_ihl_ofst_meq32, ihl_ofst_meq32 + 1)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32 + 1]);

		/* populate TCP protocol eq */
		if (attrib->ether_type == 0x0800) {
			extra = ipa_write_8(30, extra);
			rest = ipa_write_32(0xFF0000, rest);
			rest = ipa_write_32(0x60000, rest);
		} else {
			extra = ipa_write_8(26, extra);
			rest = ipa_write_32(0xFF00, rest);
			rest = ipa_write_32(0x600, rest);
		}

		/* populate TCP SYN eq */
		if (attrib->ether_type == 0x0800) {
			extra = ipa_write_8(54, extra);
			rest = ipa_write_32(0x20000, rest);
			rest = ipa_write_32(0x20000, rest);
		} else {
			extra = ipa_write_8(74, extra);
			rest = ipa_write_32(0x20000, rest);
			rest = ipa_write_32(0x20000, rest);
		}
		ihl_ofst_meq32 += 2;
	}

	if (attrib->attrib_mask & IPA_FLT_L2TP_INNER_IP_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 22  => offset of IP type after v6 header */
		extra = ipa_write_8(22, extra);
		rest = ipa_write_32(0xF0000000, rest);
		if (attrib->type == 0x40)
			rest = ipa_write_32(0x40000000, rest);
		else
			rest = ipa_write_32(0x60000000, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_L2TP_INNER_IPV4_DST_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 38  => offset of inner IPv4 addr */
		extra = ipa_write_8(38, extra);
		rest = ipa_write_32(attrib->u.v4.dst_addr_mask, rest);
		rest = ipa_write_32(attrib->u.v4.dst_addr, rest);
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_META_DATA) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_METADATA_COMPARE);
		rest = ipa_write_32(attrib->meta_data_mask, rest);
		rest = ipa_write_32(attrib->meta_data, rest);
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 0  => offset of src port after v6 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_16(attrib->src_port, rest);
		rest = ipa_write_16(attrib->src_port, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 2  => offset of dst port after v6 header */
		extra = ipa_write_8(2, extra);
		rest = ipa_write_16(attrib->dst_port, rest);
		rest = ipa_write_16(attrib->dst_port, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		if (attrib->src_port_hi < attrib->src_port_lo) {
			IPAHAL_ERR("bad src port range param\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 0  => offset of src port after v6 header */
		extra = ipa_write_8(0, extra);
		rest = ipa_write_16(attrib->src_port_hi, rest);
		rest = ipa_write_16(attrib->src_port_lo, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		if (attrib->dst_port_hi < attrib->dst_port_lo) {
			IPAHAL_ERR("bad dst port range param\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 2  => offset of dst port after v6 header */
		extra = ipa_write_8(2, extra);
		rest = ipa_write_16(attrib->dst_port_hi, rest);
		rest = ipa_write_16(attrib->dst_port_lo, rest);
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			goto err;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		/* 20  => offset of Ethertype after v4 header */
		if (attrib->ether_type == 0x0800) {
			extra = ipa_write_8(21, extra);
			rest = ipa_write_16(0x0045, rest);
			rest = ipa_write_16(0x0045, rest);
		} else {
			extra = ipa_write_8(20, extra);
			rest = ipa_write_16(attrib->ether_type, rest);
			rest = ipa_write_16(attrib->ether_type, rest);
		}
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_FLOW_LABEL) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_FL_EQ);
		rest = ipa_write_32(attrib->u.v6.flow_label & 0xFFFFF,
			rest);
	}

	if (attrib->attrib_mask & IPA_FLT_FRAGMENT)
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_FRAG);

	goto done;

err:
	rc = -EPERM;
done:
	*extra_wrds = extra;
	*rest_wrds = rest;
	return rc;
}

static u8 *ipa_fltrt_copy_mem(u8 *src, u8 *dst, int cnt)
{
	while (cnt--)
		*dst++ = *src++;

	return dst;
}

/*
 * ipa_fltrt_generate_hw_rule_bdy() - generate HW rule body (w/o header)
 * @ip: IP address type
 * @attrib: IPA rule attribute
 * @buf: output buffer. Advance it after building the rule
 * @en_rule: enable rule
 *
 * Return codes:
 * 0: success
 * -EPERM: wrong input
 */
static int ipa_fltrt_generate_hw_rule_bdy(enum ipa_ip_type ipt,
	const struct ipa_rule_attrib *attrib, u8 **buf, u16 *en_rule)
{
	int sz;
	int rc = 0;
	u8 *extra_wrd_buf;
	u8 *rest_wrd_buf;
	u8 *extra_wrd_start;
	u8 *rest_wrd_start;
	u8 *extra_wrd_i;
	u8 *rest_wrd_i;

	sz = IPA3_0_HW_TBL_WIDTH * 2 + IPA3_0_HW_RULE_START_ALIGNMENT;
	extra_wrd_buf = kzalloc(sz, GFP_KERNEL);
	if (!extra_wrd_buf) {
		rc = -ENOMEM;
		goto fail_extra_alloc;
	}

	sz = IPA3_0_HW_RULE_BUF_SIZE + IPA3_0_HW_RULE_START_ALIGNMENT;
	rest_wrd_buf = kzalloc(sz, GFP_KERNEL);
	if (!rest_wrd_buf) {
		rc = -ENOMEM;
		goto fail_rest_alloc;
	}

	extra_wrd_start = extra_wrd_buf + IPA3_0_HW_RULE_START_ALIGNMENT;
	extra_wrd_start = (u8 *)((long)extra_wrd_start &
		~IPA3_0_HW_RULE_START_ALIGNMENT);

	rest_wrd_start = rest_wrd_buf + IPA3_0_HW_RULE_START_ALIGNMENT;
	rest_wrd_start = (u8 *)((long)rest_wrd_start &
		~IPA3_0_HW_RULE_START_ALIGNMENT);

	extra_wrd_i = extra_wrd_start;
	rest_wrd_i = rest_wrd_start;

	rc = ipa_fltrt_rule_generation_err_check(ipt, attrib);
	if (rc) {
		IPAHAL_ERR_RL("rule generation err check failed\n");
		goto fail_err_check;
	}

	if (ipt == IPA_IP_v4) {
		if (ipa_fltrt_generate_hw_rule_bdy_ip4(en_rule, attrib,
			&extra_wrd_i, &rest_wrd_i)) {
			IPAHAL_ERR_RL("failed to build ipv4 hw rule\n");
			rc = -EPERM;
			goto fail_err_check;
		}

	} else if (ipt == IPA_IP_v6) {
		if (ipa_fltrt_generate_hw_rule_bdy_ip6(en_rule, attrib,
			&extra_wrd_i, &rest_wrd_i)) {
			IPAHAL_ERR_RL("failed to build ipv6 hw rule\n");
			rc = -EPERM;
			goto fail_err_check;
		}
	} else {
		IPAHAL_ERR_RL("unsupported ip %d\n", ipt);
		goto fail_err_check;
	}

	/*
	 * default "rule" means no attributes set -> map to
	 * OFFSET_MEQ32_0 with mask of 0 and val of 0 and offset 0
	 */
	if (attrib->attrib_mask == 0) {
		IPAHAL_DBG_LOW("building default rule\n");
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(ipa3_0_ofst_meq32[0]);
		extra_wrd_i = ipa_write_8(0, extra_wrd_i);  /* offset */
		rest_wrd_i = ipa_write_32(0, rest_wrd_i);   /* mask */
		rest_wrd_i = ipa_write_32(0, rest_wrd_i);   /* val */
	}

	IPAHAL_DBG_LOW("extra_word_1 0x%llx\n", *(u64 *)extra_wrd_start);
	IPAHAL_DBG_LOW("extra_word_2 0x%llx\n",
		*(u64 *)(extra_wrd_start + IPA3_0_HW_TBL_WIDTH));

	extra_wrd_i = ipa_pad_to_64(extra_wrd_i);
	sz = extra_wrd_i - extra_wrd_start;
	IPAHAL_DBG_LOW("extra words params sz %d\n", sz);
	*buf = ipa_fltrt_copy_mem(extra_wrd_start, *buf, sz);

	rest_wrd_i = ipa_pad_to_64(rest_wrd_i);
	sz = rest_wrd_i - rest_wrd_start;
	IPAHAL_DBG_LOW("non extra words params sz %d\n", sz);
	*buf = ipa_fltrt_copy_mem(rest_wrd_start, *buf, sz);

fail_err_check:
	kfree(rest_wrd_buf);
fail_rest_alloc:
	kfree(extra_wrd_buf);
fail_extra_alloc:
	return rc;
}


/**
 * ipa_fltrt_calc_extra_wrd_bytes()- Calculate the number of extra words for eq
 * @attrib: equation attribute
 *
 * Return value: 0 on success, negative otherwise
 */
static int ipa_fltrt_calc_extra_wrd_bytes(
	const struct ipa_ipfltri_rule_eq *attrib)
{
	int num = 0;

	/*
	 * tos_eq_present field has two meanings:
	 * tos equation for IPA ver < 4.5 (as the field name reveals)
	 * pure_ack equation for IPA ver >= 4.5
	 * In both cases it needs one extra word.
	 */
	if (attrib->tos_eq_present)
		num++;
	if (attrib->protocol_eq_present)
		num++;
	if (attrib->tc_eq_present)
		num++;
	num += attrib->num_offset_meq_128;
	num += attrib->num_offset_meq_32;
	num += attrib->num_ihl_offset_meq_32;
	num += attrib->num_ihl_offset_range_16;
	if (attrib->ihl_offset_eq_32_present)
		num++;
	if (attrib->ihl_offset_eq_16_present)
		num++;

	IPAHAL_DBG_LOW("extra bytes number %d\n", num);

	return num;
}

static int ipa_fltrt_generate_hw_rule_bdy_from_eq(
		const struct ipa_ipfltri_rule_eq *attrib, u8 **buf)
{
	uint8_t num_offset_meq_32 = attrib->num_offset_meq_32;
	uint8_t num_ihl_offset_range_16 = attrib->num_ihl_offset_range_16;
	uint8_t num_ihl_offset_meq_32 = attrib->num_ihl_offset_meq_32;
	uint8_t num_offset_meq_128 = attrib->num_offset_meq_128;
	int i;
	int extra_bytes;
	u8 *extra;
	u8 *rest;

	extra_bytes = ipa_fltrt_calc_extra_wrd_bytes(attrib);
	/* only 3 eq does not have extra word param, 13 out of 16 is the number
	 * of equations that needs extra word param
	 */
	if (extra_bytes > 13) {
		IPAHAL_ERR_RL("too much extra bytes\n");
		return -EPERM;
	} else if (extra_bytes > IPA3_0_HW_TBL_HDR_WIDTH) {
		/* two extra words */
		extra = *buf;
		rest = *buf + IPA3_0_HW_TBL_HDR_WIDTH * 2;
	} else if (extra_bytes > 0) {
		/* single exra word */
		extra = *buf;
		rest = *buf + IPA3_0_HW_TBL_HDR_WIDTH;
	} else {
		/* no extra words */
		extra = NULL;
		rest = *buf;
	}

	/*
	 * tos_eq_present field has two meanings:
	 * tos equation for IPA ver < 4.5 (as the field name reveals)
	 * pure_ack equation for IPA ver >= 4.5
	 * In both cases it needs one extra word.
	 */
	if (attrib->tos_eq_present) {
		if (IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK)) {
			extra = ipa_write_8(0, extra);
		} else if (IPA_IS_RULE_EQ_VALID(IPA_TOS_EQ)) {
			extra = ipa_write_8(attrib->tos_eq, extra);
		} else {
			IPAHAL_ERR("no support for pure_ack and tos eqs\n");
			return -EPERM;
		}
	}

	if (attrib->protocol_eq_present)
		extra = ipa_write_8(attrib->protocol_eq, extra);

	if (attrib->tc_eq_present)
		extra = ipa_write_8(attrib->tc_eq, extra);

	if (num_offset_meq_128) {
		extra = ipa_write_8(attrib->offset_meq_128[0].offset, extra);
		for (i = 0; i < 8; i++)
			rest = ipa_write_8(attrib->offset_meq_128[0].mask[i],
				rest);
		for (i = 0; i < 8; i++)
			rest = ipa_write_8(attrib->offset_meq_128[0].value[i],
				rest);
		for (i = 8; i < 16; i++)
			rest = ipa_write_8(attrib->offset_meq_128[0].mask[i],
				rest);
		for (i = 8; i < 16; i++)
			rest = ipa_write_8(attrib->offset_meq_128[0].value[i],
				rest);
		num_offset_meq_128--;
	}

	if (num_offset_meq_128) {
		extra = ipa_write_8(attrib->offset_meq_128[1].offset, extra);
		for (i = 0; i < 8; i++)
			rest = ipa_write_8(attrib->offset_meq_128[1].mask[i],
				rest);
		for (i = 0; i < 8; i++)
			rest = ipa_write_8(attrib->offset_meq_128[1].value[i],
				rest);
		for (i = 8; i < 16; i++)
			rest = ipa_write_8(attrib->offset_meq_128[1].mask[i],
				rest);
		for (i = 8; i < 16; i++)
			rest = ipa_write_8(attrib->offset_meq_128[1].value[i],
				rest);
		num_offset_meq_128--;
	}

	if (num_offset_meq_32) {
		extra = ipa_write_8(attrib->offset_meq_32[0].offset, extra);
		rest = ipa_write_32(attrib->offset_meq_32[0].mask, rest);
		rest = ipa_write_32(attrib->offset_meq_32[0].value, rest);
		num_offset_meq_32--;
	}

	if (num_offset_meq_32) {
		extra = ipa_write_8(attrib->offset_meq_32[1].offset, extra);
		rest = ipa_write_32(attrib->offset_meq_32[1].mask, rest);
		rest = ipa_write_32(attrib->offset_meq_32[1].value, rest);
		num_offset_meq_32--;
	}

	if (num_ihl_offset_meq_32) {
		extra = ipa_write_8(attrib->ihl_offset_meq_32[0].offset,
		extra);

		rest = ipa_write_32(attrib->ihl_offset_meq_32[0].mask, rest);
		rest = ipa_write_32(attrib->ihl_offset_meq_32[0].value, rest);
		num_ihl_offset_meq_32--;
	}

	if (num_ihl_offset_meq_32) {
		extra = ipa_write_8(attrib->ihl_offset_meq_32[1].offset,
		extra);

		rest = ipa_write_32(attrib->ihl_offset_meq_32[1].mask, rest);
		rest = ipa_write_32(attrib->ihl_offset_meq_32[1].value, rest);
		num_ihl_offset_meq_32--;
	}

	if (attrib->metadata_meq32_present) {
		rest = ipa_write_32(attrib->metadata_meq32.mask, rest);
		rest = ipa_write_32(attrib->metadata_meq32.value, rest);
	}

	if (num_ihl_offset_range_16) {
		extra = ipa_write_8(attrib->ihl_offset_range_16[0].offset,
		extra);

		rest = ipa_write_16(attrib->ihl_offset_range_16[0].range_high,
				rest);
		rest = ipa_write_16(attrib->ihl_offset_range_16[0].range_low,
				rest);
		num_ihl_offset_range_16--;
	}

	if (num_ihl_offset_range_16) {
		extra = ipa_write_8(attrib->ihl_offset_range_16[1].offset,
		extra);

		rest = ipa_write_16(attrib->ihl_offset_range_16[1].range_high,
				rest);
		rest = ipa_write_16(attrib->ihl_offset_range_16[1].range_low,
				rest);
		num_ihl_offset_range_16--;
	}

	if (attrib->ihl_offset_eq_32_present) {
		extra = ipa_write_8(attrib->ihl_offset_eq_32.offset, extra);
		rest = ipa_write_32(attrib->ihl_offset_eq_32.value, rest);
	}

	if (attrib->ihl_offset_eq_16_present) {
		extra = ipa_write_8(attrib->ihl_offset_eq_16.offset, extra);
		rest = ipa_write_16(attrib->ihl_offset_eq_16.value, rest);
		rest = ipa_write_16(0, rest);
	}

	if (attrib->fl_eq_present)
		rest = ipa_write_32(attrib->fl_eq & 0xFFFFF, rest);

	if (extra)
		extra = ipa_pad_to_64(extra);
	rest = ipa_pad_to_64(rest);
	*buf = rest;

	return 0;
}

static void ipa_flt_generate_mac_addr_eq(struct ipa_ipfltri_rule_eq *eq_atrb,
	u8 hdr_mac_addr_offset,	const uint8_t mac_addr_mask[ETH_ALEN],
	const uint8_t mac_addr[ETH_ALEN], u8 ofst_meq128)
{
	int i;

	eq_atrb->offset_meq_128[ofst_meq128].offset = hdr_mac_addr_offset;

	/* LSB MASK and ADDR */
	memset(eq_atrb->offset_meq_128[ofst_meq128].mask, 0, 8);
	memset(eq_atrb->offset_meq_128[ofst_meq128].value, 0, 8);

	/* MSB MASK and ADDR */
	memset(eq_atrb->offset_meq_128[ofst_meq128].mask + 8, 0, 2);
	for (i = 0; i <= 5; i++)
		eq_atrb->offset_meq_128[ofst_meq128].mask[15 - i] =
			mac_addr_mask[i];

	memset(eq_atrb->offset_meq_128[ofst_meq128].value + 8, 0, 2);
	for (i = 0; i <= 5; i++)
		eq_atrb->offset_meq_128[ofst_meq128].value[15 - i] =
			mac_addr[i];
}

static int ipa_flt_generate_mac_eq(
	const struct ipa_rule_attrib *attrib, u16 *en_rule, u8 *ofst_meq128,
	struct ipa_ipfltri_rule_eq *eq_atrb)
{
	u8 offset = 0;
	const uint8_t *mac_addr = NULL;
	const uint8_t *mac_addr_mask = NULL;
	int i;
	uint32_t attrib_mask;

	for (i = 0; i < hweight_long(IPA_MAC_FLT_BITS); i++) {
		switch (i) {
		case 0:
			attrib_mask = IPA_FLT_MAC_DST_ADDR_ETHER_II;
			break;
		case 1:
			attrib_mask = IPA_FLT_MAC_SRC_ADDR_ETHER_II;
			break;
		case 2:
			attrib_mask = IPA_FLT_MAC_DST_ADDR_802_3;
			break;
		case 3:
			attrib_mask = IPA_FLT_MAC_SRC_ADDR_802_3;
			break;
		case 4:
			attrib_mask = IPA_FLT_MAC_DST_ADDR_802_1Q;
			break;
		case 5:
			attrib_mask = IPA_FLT_MAC_SRC_ADDR_802_1Q;
			break;
		default:
			return -EPERM;
		}

		attrib_mask &= attrib->attrib_mask;
		if (!attrib_mask)
			continue;

		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, *ofst_meq128)) {
			IPAHAL_ERR("ran out of meq128 eq\n");
			return -EPERM;
		}

		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[*ofst_meq128]);

		ipa_fltrt_get_mac_data(attrib, attrib_mask, &offset,
			&mac_addr, &mac_addr_mask);

		ipa_flt_generate_mac_addr_eq(eq_atrb, offset,
			mac_addr_mask, mac_addr,
			*ofst_meq128);

		(*ofst_meq128)++;
	}

	return 0;
}

static inline int ipa_flt_generat_vlan_eq(
	const struct ipa_rule_attrib *attrib, u16 *en_rule, u8 *ofst_meq32,
	struct ipa_ipfltri_rule_eq *eq_atrb)
{
	if (attrib->attrib_mask & IPA_FLT_VLAN_ID) {
		uint32_t vlan_tag;

		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, *ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[*ofst_meq32]);
		/* -6 => offset of 802_1Q tag in L2 hdr */
		eq_atrb->offset_meq_32[*ofst_meq32].offset = -6;
		/* filter vlan packets: 0x8100 TPID + required VLAN ID */
		vlan_tag = (0x8100 << 16) | (attrib->vlan_id & 0xFFF);
		eq_atrb->offset_meq_32[*ofst_meq32].mask = 0xFFFF0FFF;
		eq_atrb->offset_meq_32[*ofst_meq32].value = vlan_tag;
		(*ofst_meq32)++;
	}

	return 0;
}

static int ipa_flt_generate_eq_ip4(enum ipa_ip_type ip,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb)
{
	u8 ofst_meq32 = 0;
	u8 ihl_ofst_rng16 = 0;
	u8 ihl_ofst_meq32 = 0;
	u8 ofst_meq128 = 0;
	u16 eq_bitmap = 0;
	u16 *en_rule = &eq_bitmap;
	bool tos_done = false;

	if (attrib->attrib_mask & IPA_FLT_IS_PURE_ACK) {
		if (!IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK)) {
			IPAHAL_ERR("is_pure_ack eq not supported\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_PURE_ACK);
		/*
		 * Starting IPA 4.5, where PURE ACK equation supported
		 * and TOS equation support removed, field tos_eq_present
		 * represent pure_ack presence.
		 */
		eq_atrb->tos_eq_present = 1;
		eq_atrb->tos_eq = 0;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		if (!IPA_IS_RULE_EQ_VALID(IPA_TOS_EQ)) {
			IPAHAL_DBG("tos eq not supported\n");
		} else {
			*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_TOS_EQ);
			eq_atrb->tos_eq_present = 1;
			eq_atrb->tos_eq = attrib->u.v4.tos;
		}
	}

	if (attrib->attrib_mask & IPA_FLT_PROTOCOL) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_PROTOCOL_EQ);
		eq_atrb->protocol_eq_present = 1;
		eq_atrb->protocol_eq = attrib->u.v4.protocol;
	}

	if (attrib->attrib_mask & IPA_MAC_FLT_BITS) {
		if (ipa_flt_generate_mac_eq(attrib, en_rule,
			&ofst_meq128, eq_atrb))
			return -EPERM;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_DST_ADDR_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32) || IPA_IS_RAN_OUT_OF_EQ(
			ipa3_0_ihl_ofst_meq32, ihl_ofst_meq32 + 1)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32 + 1]);
		/* populate the first ihl meq 32 eq */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 8;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
			(attrib->dst_mac_addr_mask[3] & 0xFF) |
			((attrib->dst_mac_addr_mask[2] << 8) & 0xFF00) |
			((attrib->dst_mac_addr_mask[1] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr_mask[0] << 24) & 0xFF000000);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			(attrib->dst_mac_addr[3] & 0xFF) |
			((attrib->dst_mac_addr[2] << 8) & 0xFF00) |
			((attrib->dst_mac_addr[1] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr[0] << 24) & 0xFF000000);
		/* populate the second ihl meq 32 eq */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32 + 1].offset = 12;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32 + 1].mask =
			((attrib->dst_mac_addr_mask[5] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr_mask[4] << 24) & 0xFF000000);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32 + 1].value =
			((attrib->dst_mac_addr[5] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr[4] << 24) & 0xFF000000);
		ihl_ofst_meq32 += 2;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 12  => offset of SYN after v4 header */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 12;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask = 0x20000;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value = 0x20000;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS_MASKED) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		eq_atrb->offset_meq_32[ofst_meq32].offset = 0;
		eq_atrb->offset_meq_32[ofst_meq32].mask =
			attrib->tos_mask << 16;
		eq_atrb->offset_meq_32[ofst_meq32].value =
			attrib->tos_value << 16;
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		eq_atrb->offset_meq_32[ofst_meq32].offset = 12;
		eq_atrb->offset_meq_32[ofst_meq32].mask =
			attrib->u.v4.src_addr_mask;
		eq_atrb->offset_meq_32[ofst_meq32].value =
			attrib->u.v4.src_addr;
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		eq_atrb->offset_meq_32[ofst_meq32].offset = 16;
		eq_atrb->offset_meq_32[ofst_meq32].mask =
			attrib->u.v4.dst_addr_mask;
		eq_atrb->offset_meq_32[ofst_meq32].value =
			attrib->u.v4.dst_addr;
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_ETHER_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		eq_atrb->offset_meq_32[ofst_meq32].offset = -2;
		eq_atrb->offset_meq_32[ofst_meq32].mask =
			htons(attrib->ether_type);
		eq_atrb->offset_meq_32[ofst_meq32].value =
			htons(attrib->ether_type);
		ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_DBG("ran out of meq32 eq\n");
		} else {
			*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
				ipa3_0_ofst_meq32[ofst_meq32]);
			/*
			 * offset 0 => Take the first word.
			 * offset of TOS in v4 header is 1
			 */
			eq_atrb->offset_meq_32[ofst_meq32].offset = 0;
			eq_atrb->offset_meq_32[ofst_meq32].mask =
				0xFF << 16;
			eq_atrb->offset_meq_32[ofst_meq32].value =
				attrib->u.v4.tos << 16;
			ofst_meq32++;
			tos_done = true;
		}
	}

	if (ipa_flt_generat_vlan_eq(attrib, en_rule, &ofst_meq32, eq_atrb))
		return -EPERM;

	if (attrib->attrib_mask & IPA_FLT_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 0;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask = 0xFF;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->type;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_CODE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 1;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask = 0xFF;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->code;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_SPI) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 0;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
			0xFFFFFFFF;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->spi;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_DBG("ran out of ihl_meq32 eq\n");
		} else {
			*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
				ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
			/*
			 * 0 => Take the first word. offset of TOS in
			 * v4 header is 1. MSB bit asserted at IHL means
			 * to ignore packet IHL and do offset inside IPA header
			 */
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset =
				0x80;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
				0xFF << 16;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
				attrib->u.v4.tos << 16;
			ihl_ofst_meq32++;
			tos_done = true;
		}
	}

	if (attrib->attrib_mask & IPA_FLT_META_DATA) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			IPA_METADATA_COMPARE);
		eq_atrb->metadata_meq32_present = 1;
		eq_atrb->metadata_meq32.offset = 0;
		eq_atrb->metadata_meq32.mask = attrib->meta_data_mask;
		eq_atrb->metadata_meq32.value = attrib->meta_data;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		if (attrib->src_port_hi < attrib->src_port_lo) {
			IPAHAL_ERR("bad src port range param\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 0;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->src_port_lo;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->src_port_hi;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		if (attrib->dst_port_hi < attrib->dst_port_lo) {
			IPAHAL_ERR("bad dst port range param\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 2;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->dst_port_lo;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->dst_port_hi;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 0;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->src_port;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->src_port;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 2;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->dst_port;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->dst_port;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_FRAGMENT) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_FRAG);
		eq_atrb->ipv4_frag_eq_present = 1;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS && !tos_done) {
		IPAHAL_ERR("could not find equation for tos\n");
		return -EPERM;
	}

	eq_atrb->rule_eq_bitmap = *en_rule;
	eq_atrb->num_offset_meq_32 = ofst_meq32;
	eq_atrb->num_ihl_offset_range_16 = ihl_ofst_rng16;
	eq_atrb->num_ihl_offset_meq_32 = ihl_ofst_meq32;
	eq_atrb->num_offset_meq_128 = ofst_meq128;

	return 0;
}

static int ipa_flt_generate_eq_ip6(enum ipa_ip_type ip,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb)
{
	u8 ofst_meq32 = 0;
	u8 ihl_ofst_rng16 = 0;
	u8 ihl_ofst_meq32 = 0;
	u8 ofst_meq128 = 0;
	u16 eq_bitmap = 0;
	u16 *en_rule = &eq_bitmap;

	if (attrib->attrib_mask & IPA_FLT_IS_PURE_ACK) {
		if (!IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK)) {
			IPAHAL_ERR("is_pure_ack eq not supported\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_PURE_ACK);
		/*
		 * Starting IPA 4.5, where PURE ACK equation supported
		 * and TOS equation support removed, field tos_eq_present
		 * represent pure_ack presenence.
		 */
		eq_atrb->tos_eq_present = 1;
		eq_atrb->tos_eq = 0;
	}

	if (attrib->attrib_mask & IPA_FLT_NEXT_HDR) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			IPA_PROTOCOL_EQ);
		eq_atrb->protocol_eq_present = 1;
		eq_atrb->protocol_eq = attrib->u.v6.next_hdr;
	}

	if (attrib->attrib_mask & IPA_FLT_TC) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			IPA_TC_EQ);
		eq_atrb->tc_eq_present = 1;
		eq_atrb->tc_eq = attrib->u.v6.tc;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, ofst_meq128)) {
			IPAHAL_ERR_RL("ran out of meq128 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[ofst_meq128]);
		/* use the same word order as in ipa v2 */
		eq_atrb->offset_meq_128[ofst_meq128].offset = 8;
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 0)
			= attrib->u.v6.src_addr_mask[0];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 4)
			= attrib->u.v6.src_addr_mask[1];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 8)
			= attrib->u.v6.src_addr_mask[2];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 12)
			= attrib->u.v6.src_addr_mask[3];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value + 0)
			= attrib->u.v6.src_addr[0];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value + 4)
			= attrib->u.v6.src_addr[1];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value + 8)
			= attrib->u.v6.src_addr[2];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value +
				12) = attrib->u.v6.src_addr[3];
		ofst_meq128++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, ofst_meq128)) {
			IPAHAL_ERR_RL("ran out of meq128 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[ofst_meq128]);
		eq_atrb->offset_meq_128[ofst_meq128].offset = 24;
		/* use the same word order as in ipa v2 */
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 0)
			= attrib->u.v6.dst_addr_mask[0];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 4)
			= attrib->u.v6.dst_addr_mask[1];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 8)
			= attrib->u.v6.dst_addr_mask[2];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 12)
			= attrib->u.v6.dst_addr_mask[3];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value + 0)
			= attrib->u.v6.dst_addr[0];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value + 4)
			= attrib->u.v6.dst_addr[1];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value + 8)
			= attrib->u.v6.dst_addr[2];
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value +
				12) = attrib->u.v6.dst_addr[3];
		ofst_meq128++;
	}

	if (attrib->attrib_mask & IPA_FLT_TOS_MASKED) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq128, ofst_meq128)) {
			IPAHAL_ERR_RL("ran out of meq128 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq128[ofst_meq128]);
		eq_atrb->offset_meq_128[ofst_meq128].offset = 0;
		memset(eq_atrb->offset_meq_128[ofst_meq128].mask, 0, 12);
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].mask + 12)
			= attrib->tos_mask << 20;
		memset(eq_atrb->offset_meq_128[ofst_meq128].value, 0, 12);
		*(u32 *)(eq_atrb->offset_meq_128[ofst_meq128].value +
				12) = attrib->tos_value << 20;
		ofst_meq128++;
	}

	if (attrib->attrib_mask & IPA_MAC_FLT_BITS) {
		if (ipa_flt_generate_mac_eq(attrib, en_rule,
			&ofst_meq128, eq_atrb))
			return -EPERM;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_DST_ADDR_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32) || IPA_IS_RAN_OUT_OF_EQ(
			ipa3_0_ihl_ofst_meq32, ihl_ofst_meq32 + 1)) {
			IPAHAL_ERR_RL("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32 + 1]);
		/* populate the first ihl meq 32 eq */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 8;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
			(attrib->dst_mac_addr_mask[3] & 0xFF) |
			((attrib->dst_mac_addr_mask[2] << 8) & 0xFF00) |
			((attrib->dst_mac_addr_mask[1] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr_mask[0] << 24) & 0xFF000000);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			(attrib->dst_mac_addr[3] & 0xFF) |
			((attrib->dst_mac_addr[2] << 8) & 0xFF00) |
			((attrib->dst_mac_addr[1] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr[0] << 24) & 0xFF000000);
		/* populate the second ihl meq 32 eq */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32 + 1].offset = 12;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32 + 1].mask =
			((attrib->dst_mac_addr_mask[5] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr_mask[4] << 24) & 0xFF000000);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32 + 1].value =
			((attrib->dst_mac_addr[5] << 16) & 0xFF0000) |
			((attrib->dst_mac_addr[4] << 24) & 0xFF000000);
		ihl_ofst_meq32 += 2;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR_RL("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 12  => offset of SYN after v4 header */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 12;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask = 0x20000;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value = 0x20000;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32) || IPA_IS_RAN_OUT_OF_EQ(
			ipa3_0_ihl_ofst_meq32, ihl_ofst_meq32 + 1)) {
			IPAHAL_ERR_RL("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32 + 1]);

		/* populate TCP protocol eq */
		if (attrib->ether_type == 0x0800) {
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 30;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
				0xFF0000;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
				0x60000;
		} else {
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 26;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
				0xFF00;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
				0x600;
		}

		/* populate TCP SYN eq */
		if (attrib->ether_type == 0x0800) {
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 54;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
				0x20000;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
				0x20000;
		} else {
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 74;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
				0x20000;
			eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
				0x20000;
		}
		ihl_ofst_meq32 += 2;
	}

	if (attrib->attrib_mask & IPA_FLT_L2TP_INNER_IP_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 22  => offset of inner IP type after v6 header */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 22;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
			0xF0000000;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			(u32)attrib->type << 24;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_L2TP_INNER_IPV4_DST_ADDR) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		/* 38  => offset of inner IPv4 addr */
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 38;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
			attrib->u.v4.dst_addr_mask;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->u.v4.dst_addr;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_MAC_ETHER_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ofst_meq32, ofst_meq32)) {
			IPAHAL_ERR_RL("ran out of meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ofst_meq32[ofst_meq32]);
		eq_atrb->offset_meq_32[ofst_meq32].offset = -2;
		eq_atrb->offset_meq_32[ofst_meq32].mask =
			htons(attrib->ether_type);
		eq_atrb->offset_meq_32[ofst_meq32].value =
			htons(attrib->ether_type);
		ofst_meq32++;
	}

	if (ipa_flt_generat_vlan_eq(attrib, en_rule, &ofst_meq32, eq_atrb))
		return -EPERM;

	if (attrib->attrib_mask & IPA_FLT_TYPE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR_RL("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 0;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask = 0xFF;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->type;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_CODE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR_RL("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 1;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask = 0xFF;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->code;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_SPI) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_meq32,
			ihl_ofst_meq32)) {
			IPAHAL_ERR_RL("ran out of ihl_meq32 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_meq32[ihl_ofst_meq32]);
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].offset = 0;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].mask =
			0xFFFFFFFF;
		eq_atrb->ihl_offset_meq_32[ihl_ofst_meq32].value =
			attrib->spi;
		ihl_ofst_meq32++;
	}

	if (attrib->attrib_mask & IPA_FLT_META_DATA) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			IPA_METADATA_COMPARE);
		eq_atrb->metadata_meq32_present = 1;
		eq_atrb->metadata_meq32.offset = 0;
		eq_atrb->metadata_meq32.mask = attrib->meta_data_mask;
		eq_atrb->metadata_meq32.value = attrib->meta_data;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR_RL("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 0;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->src_port;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->src_port;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR_RL("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 2;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->dst_port;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->dst_port;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_SRC_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR_RL("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		if (attrib->src_port_hi < attrib->src_port_lo) {
			IPAHAL_ERR_RL("bad src port range param\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 0;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->src_port_lo;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->src_port_hi;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_DST_PORT_RANGE) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR_RL("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		if (attrib->dst_port_hi < attrib->dst_port_lo) {
			IPAHAL_ERR_RL("bad dst port range param\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset = 2;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
			= attrib->dst_port_lo;
		eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
			= attrib->dst_port_hi;
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		if (attrib->ether_type == 0x0800) {
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset
				= 21;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
				= 0x0045;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
				= 0x0045;
		} else {
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset =
				20;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
				= attrib->ether_type;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
				= attrib->ether_type;
		}
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_TCP_SYN_L2TP) {
		if (IPA_IS_RAN_OUT_OF_EQ(ipa3_0_ihl_ofst_rng16,
				ihl_ofst_rng16)) {
			IPAHAL_ERR_RL("ran out of ihl_rng16 eq\n");
			return -EPERM;
		}
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			ipa3_0_ihl_ofst_rng16[ihl_ofst_rng16]);
		if (attrib->ether_type == 0x0800) {
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset
				= 21;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
				= 0x0045;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
				= 0x0045;
		} else {
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].offset =
				20;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_low
				= attrib->ether_type;
			eq_atrb->ihl_offset_range_16[ihl_ofst_rng16].range_high
				= attrib->ether_type;
		}
		ihl_ofst_rng16++;
	}

	if (attrib->attrib_mask & IPA_FLT_FLOW_LABEL) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(IPA_FL_EQ);
		eq_atrb->fl_eq_present = 1;
		eq_atrb->fl_eq = attrib->u.v6.flow_label;
	}

	if (attrib->attrib_mask & IPA_FLT_FRAGMENT) {
		*en_rule |= IPA_GET_RULE_EQ_BIT_PTRN(
			IPA_IS_FRAG);
		eq_atrb->ipv4_frag_eq_present = 1;
	}

	eq_atrb->rule_eq_bitmap = *en_rule;
	eq_atrb->num_offset_meq_32 = ofst_meq32;
	eq_atrb->num_ihl_offset_range_16 = ihl_ofst_rng16;
	eq_atrb->num_ihl_offset_meq_32 = ihl_ofst_meq32;
	eq_atrb->num_offset_meq_128 = ofst_meq128;

	return 0;
}

static int ipa_fltrt_parse_hw_rule_eq(u8 *addr, u32 hdr_sz,
	struct ipa_ipfltri_rule_eq *atrb, u32 *rule_size)
{
	u16 eq_bitmap;
	int extra_bytes;
	u8 *extra;
	u8 *rest;
	int i;
	u8 dummy_extra_wrd;

	if (!addr || !atrb || !rule_size) {
		IPAHAL_ERR("Input error: addr=%pK atrb=%pK rule_size=%pK\n",
			addr, atrb, rule_size);
		return -EINVAL;
	}

	eq_bitmap = atrb->rule_eq_bitmap;

	IPAHAL_DBG_LOW("eq_bitmap=0x%x\n", eq_bitmap);

	if (IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK) &&
		(eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_PURE_ACK))) {
		/*
		 * tos_eq_present field represents pure_ack when pure
		 * ack equation valid (started IPA 4.5). In this case
		 * tos equation should not be supported.
		 */
		atrb->tos_eq_present = true;
	}
	if (IPA_IS_RULE_EQ_VALID(IPA_TOS_EQ) &&
		(eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_TOS_EQ))) {
		atrb->tos_eq_present = true;
	}
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_PROTOCOL_EQ))
		atrb->protocol_eq_present = true;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_TC_EQ))
		atrb->tc_eq_present = true;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_OFFSET_MEQ128_0))
		atrb->num_offset_meq_128++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_OFFSET_MEQ128_1))
		atrb->num_offset_meq_128++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_OFFSET_MEQ32_0))
		atrb->num_offset_meq_32++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_OFFSET_MEQ32_1))
		atrb->num_offset_meq_32++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IHL_OFFSET_MEQ32_0))
		atrb->num_ihl_offset_meq_32++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IHL_OFFSET_MEQ32_1))
		atrb->num_ihl_offset_meq_32++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_METADATA_COMPARE))
		atrb->metadata_meq32_present = true;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IHL_OFFSET_RANGE16_0))
		atrb->num_ihl_offset_range_16++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IHL_OFFSET_RANGE16_1))
		atrb->num_ihl_offset_range_16++;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IHL_OFFSET_EQ_32))
		atrb->ihl_offset_eq_32_present = true;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IHL_OFFSET_EQ_16))
		atrb->ihl_offset_eq_16_present = true;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_FL_EQ))
		atrb->fl_eq_present = true;
	if (eq_bitmap & IPA_GET_RULE_EQ_BIT_PTRN(IPA_IS_FRAG))
		atrb->ipv4_frag_eq_present = true;

	extra_bytes = ipa_fltrt_calc_extra_wrd_bytes(atrb);
	/* only 3 eq does not have extra word param, 13 out of 16 is the number
	 * of equations that needs extra word param
	 */
	if (extra_bytes > 13) {
		IPAHAL_ERR("too much extra bytes\n");
		return -EPERM;
	} else if (extra_bytes > IPA3_0_HW_TBL_HDR_WIDTH) {
		/* two extra words */
		extra = addr + hdr_sz;
		rest = extra + IPA3_0_HW_TBL_HDR_WIDTH * 2;
	} else if (extra_bytes > 0) {
		/* single extra word */
		extra = addr + hdr_sz;
		rest = extra + IPA3_0_HW_TBL_HDR_WIDTH;
	} else {
		/* no extra words */
		dummy_extra_wrd = 0;
		extra = &dummy_extra_wrd;
		rest = addr + hdr_sz;
	}
	IPAHAL_DBG_LOW("addr=0x%pK extra=0x%pK rest=0x%pK\n",
		addr, extra, rest);

	if (IPA_IS_RULE_EQ_VALID(IPA_TOS_EQ) && atrb->tos_eq_present)
		atrb->tos_eq = *extra++;
	if (IPA_IS_RULE_EQ_VALID(IPA_IS_PURE_ACK) && atrb->tos_eq_present) {
		atrb->tos_eq = 0;
		extra++;
	}
	if (atrb->protocol_eq_present)
		atrb->protocol_eq = *extra++;
	if (atrb->tc_eq_present)
		atrb->tc_eq = *extra++;

	if (atrb->num_offset_meq_128 > 0) {
		atrb->offset_meq_128[0].offset = *extra++;
		for (i = 0; i < 8; i++)
			atrb->offset_meq_128[0].mask[i] = *rest++;
		for (i = 0; i < 8; i++)
			atrb->offset_meq_128[0].value[i] = *rest++;
		for (i = 8; i < 16; i++)
			atrb->offset_meq_128[0].mask[i] = *rest++;
		for (i = 8; i < 16; i++)
			atrb->offset_meq_128[0].value[i] = *rest++;
	}
	if (atrb->num_offset_meq_128 > 1) {
		atrb->offset_meq_128[1].offset = *extra++;
		for (i = 0; i < 8; i++)
			atrb->offset_meq_128[1].mask[i] = *rest++;
		for (i = 0; i < 8; i++)
			atrb->offset_meq_128[1].value[i] = *rest++;
		for (i = 8; i < 16; i++)
			atrb->offset_meq_128[1].mask[i] = *rest++;
		for (i = 8; i < 16; i++)
			atrb->offset_meq_128[1].value[i] = *rest++;
	}

	if (atrb->num_offset_meq_32 > 0) {
		atrb->offset_meq_32[0].offset = *extra++;
		atrb->offset_meq_32[0].mask = *((u32 *)rest);
		rest += 4;
		atrb->offset_meq_32[0].value = *((u32 *)rest);
		rest += 4;
	}
	if (atrb->num_offset_meq_32 > 1) {
		atrb->offset_meq_32[1].offset = *extra++;
		atrb->offset_meq_32[1].mask = *((u32 *)rest);
		rest += 4;
		atrb->offset_meq_32[1].value = *((u32 *)rest);
		rest += 4;
	}

	if (atrb->num_ihl_offset_meq_32 > 0) {
		atrb->ihl_offset_meq_32[0].offset = *extra++;
		atrb->ihl_offset_meq_32[0].mask = *((u32 *)rest);
		rest += 4;
		atrb->ihl_offset_meq_32[0].value = *((u32 *)rest);
		rest += 4;
	}
	if (atrb->num_ihl_offset_meq_32 > 1) {
		atrb->ihl_offset_meq_32[1].offset = *extra++;
		atrb->ihl_offset_meq_32[1].mask = *((u32 *)rest);
		rest += 4;
		atrb->ihl_offset_meq_32[1].value = *((u32 *)rest);
		rest += 4;
	}

	if (atrb->metadata_meq32_present) {
		atrb->metadata_meq32.mask = *((u32 *)rest);
		rest += 4;
		atrb->metadata_meq32.value = *((u32 *)rest);
		rest += 4;
	}

	if (atrb->num_ihl_offset_range_16 > 0) {
		atrb->ihl_offset_range_16[0].offset = *extra++;
		atrb->ihl_offset_range_16[0].range_high = *((u16 *)rest);
		rest += 2;
		atrb->ihl_offset_range_16[0].range_low = *((u16 *)rest);
		rest += 2;
	}
	if (atrb->num_ihl_offset_range_16 > 1) {
		atrb->ihl_offset_range_16[1].offset = *extra++;
		atrb->ihl_offset_range_16[1].range_high = *((u16 *)rest);
		rest += 2;
		atrb->ihl_offset_range_16[1].range_low = *((u16 *)rest);
		rest += 2;
	}

	if (atrb->ihl_offset_eq_32_present) {
		atrb->ihl_offset_eq_32.offset = *extra++;
		atrb->ihl_offset_eq_32.value = *((u32 *)rest);
		rest += 4;
	}

	if (atrb->ihl_offset_eq_16_present) {
		atrb->ihl_offset_eq_16.offset = *extra++;
		atrb->ihl_offset_eq_16.value = *((u16 *)rest);
		rest += 4;
	}

	if (atrb->fl_eq_present) {
		atrb->fl_eq = *((u32 *)rest);
		atrb->fl_eq &= 0xfffff;
		rest += 4;
	}

	IPAHAL_DBG_LOW("before rule alignment rest=0x%pK\n", rest);
	rest = (u8 *)(((unsigned long)rest + IPA3_0_HW_RULE_START_ALIGNMENT) &
		~IPA3_0_HW_RULE_START_ALIGNMENT);
	IPAHAL_DBG_LOW("after rule alignment  rest=0x%pK\n", rest);

	*rule_size = rest - addr;
	IPAHAL_DBG_LOW("rule_size=0x%x\n", *rule_size);

	return 0;
}

static int ipa_rt_parse_hw_rule(u8 *addr, struct ipahal_rt_rule_entry *rule)
{
	struct ipa3_0_rt_rule_hw_hdr *rule_hdr;
	struct ipa_ipfltri_rule_eq *atrb;

	IPAHAL_DBG_LOW("Entry\n");

	rule_hdr = (struct ipa3_0_rt_rule_hw_hdr *)addr;
	atrb = &rule->eq_attrib;

	IPAHAL_DBG_LOW("read hdr 0x%llx\n", rule_hdr->u.word);

	if (rule_hdr->u.word == 0) {
		/* table terminator - empty table */
		rule->rule_size = 0;
		return 0;
	}

	rule->dst_pipe_idx = rule_hdr->u.hdr.pipe_dest_idx;
	if (rule_hdr->u.hdr.proc_ctx) {
		rule->hdr_type = IPAHAL_RT_RULE_HDR_PROC_CTX;
		rule->hdr_ofst = (rule_hdr->u.hdr.hdr_offset) << 5;
	} else {
		rule->hdr_type = IPAHAL_RT_RULE_HDR_RAW;
		rule->hdr_ofst = (rule_hdr->u.hdr.hdr_offset) << 2;
	}
	rule->hdr_lcl = !rule_hdr->u.hdr.system;

	rule->priority = rule_hdr->u.hdr.priority;
	rule->retain_hdr = rule_hdr->u.hdr.retain_hdr;
	rule->id = rule_hdr->u.hdr.rule_id;

	atrb->rule_eq_bitmap = rule_hdr->u.hdr.en_rule;
	return ipa_fltrt_parse_hw_rule_eq(addr, sizeof(*rule_hdr),
		atrb, &rule->rule_size);
}

static int ipa_rt_parse_hw_rule_ipav4_5(u8 *addr,
	struct ipahal_rt_rule_entry *rule)
{
	struct ipa4_5_rt_rule_hw_hdr *rule_hdr;
	struct ipa_ipfltri_rule_eq *atrb;

	IPAHAL_DBG_LOW("Entry\n");

	rule_hdr = (struct ipa4_5_rt_rule_hw_hdr *)addr;
	atrb = &rule->eq_attrib;

	IPAHAL_DBG_LOW("read hdr 0x%llx\n", rule_hdr->u.word);

	if (rule_hdr->u.word == 0) {
		/* table termintator - empty table */
		rule->rule_size = 0;
		return 0;
	}

	rule->dst_pipe_idx = rule_hdr->u.hdr.pipe_dest_idx;
	if (rule_hdr->u.hdr.proc_ctx) {
		rule->hdr_type = IPAHAL_RT_RULE_HDR_PROC_CTX;
		rule->hdr_ofst = (rule_hdr->u.hdr.hdr_offset) << 5;
	} else {
		rule->hdr_type = IPAHAL_RT_RULE_HDR_RAW;
		rule->hdr_ofst = (rule_hdr->u.hdr.hdr_offset) << 2;
	}
	rule->hdr_lcl = !rule_hdr->u.hdr.system;

	rule->priority = rule_hdr->u.hdr.priority;
	rule->retain_hdr = rule_hdr->u.hdr.retain_hdr;
	rule->cnt_idx = rule_hdr->u.hdr.stats_cnt_idx_lsb |
		(rule_hdr->u.hdr.stats_cnt_idx_msb) << 6;
	rule->id = rule_hdr->u.hdr.rule_id;

	atrb->rule_eq_bitmap = rule_hdr->u.hdr.en_rule;
	return ipa_fltrt_parse_hw_rule_eq(addr, sizeof(*rule_hdr),
		atrb, &rule->rule_size);
}

static int ipa_flt_parse_hw_rule(u8 *addr, struct ipahal_flt_rule_entry *rule)
{
	struct ipa3_0_flt_rule_hw_hdr *rule_hdr;
	struct ipa_ipfltri_rule_eq *atrb;

	IPAHAL_DBG_LOW("Entry\n");

	rule_hdr = (struct ipa3_0_flt_rule_hw_hdr *)addr;
	atrb = &rule->rule.eq_attrib;

	if (rule_hdr->u.word == 0) {
		/* table termintator - empty table */
		rule->rule_size = 0;
		return 0;
	}

	switch (rule_hdr->u.hdr.action) {
	case 0x0:
		rule->rule.action = IPA_PASS_TO_ROUTING;
		break;
	case 0x1:
		rule->rule.action = IPA_PASS_TO_SRC_NAT;
		break;
	case 0x2:
		rule->rule.action = IPA_PASS_TO_DST_NAT;
		break;
	case 0x3:
		rule->rule.action = IPA_PASS_TO_EXCEPTION;
		break;
	default:
		IPAHAL_ERR("Invalid Rule Action %d\n", rule_hdr->u.hdr.action);
		WARN_ON_RATELIMIT_IPA(1);
		rule->rule.action = rule_hdr->u.hdr.action;
	}

	rule->rule.rt_tbl_idx = rule_hdr->u.hdr.rt_tbl_idx;
	rule->rule.retain_hdr = rule_hdr->u.hdr.retain_hdr;
	rule->priority = rule_hdr->u.hdr.priority;
	rule->id = rule_hdr->u.hdr.rule_id;

	atrb->rule_eq_bitmap = rule_hdr->u.hdr.en_rule;
	rule->rule.eq_attrib_type = 1;
	return ipa_fltrt_parse_hw_rule_eq(addr, sizeof(*rule_hdr),
		atrb, &rule->rule_size);
}

static int ipa_flt_parse_hw_rule_ipav4(u8 *addr,
	struct ipahal_flt_rule_entry *rule)
{
	struct ipa4_0_flt_rule_hw_hdr *rule_hdr;
	struct ipa_ipfltri_rule_eq *atrb;

	IPAHAL_DBG_LOW("Entry\n");

	rule_hdr = (struct ipa4_0_flt_rule_hw_hdr *)addr;
	atrb = &rule->rule.eq_attrib;

	if (rule_hdr->u.word == 0) {
		/* table termintator - empty table */
		rule->rule_size = 0;
		return 0;
	}

	switch (rule_hdr->u.hdr.action) {
	case 0x0:
		rule->rule.action = IPA_PASS_TO_ROUTING;
		break;
	case 0x1:
		rule->rule.action = IPA_PASS_TO_SRC_NAT;
		break;
	case 0x2:
		rule->rule.action = IPA_PASS_TO_DST_NAT;
		break;
	case 0x3:
		rule->rule.action = IPA_PASS_TO_EXCEPTION;
		break;
	default:
		IPAHAL_ERR("Invalid Rule Action %d\n", rule_hdr->u.hdr.action);
		WARN_ON_RATELIMIT_IPA(1);
		rule->rule.action = rule_hdr->u.hdr.action;
	}

	rule->rule.rt_tbl_idx = rule_hdr->u.hdr.rt_tbl_idx;
	rule->rule.retain_hdr = rule_hdr->u.hdr.retain_hdr;
	rule->priority = rule_hdr->u.hdr.priority;
	rule->id = rule_hdr->u.hdr.rule_id;
	rule->rule.pdn_idx = rule_hdr->u.hdr.pdn_idx;
	rule->rule.set_metadata = rule_hdr->u.hdr.set_metadata;

	atrb->rule_eq_bitmap = rule_hdr->u.hdr.en_rule;
	rule->rule.eq_attrib_type = 1;
	return ipa_fltrt_parse_hw_rule_eq(addr, sizeof(*rule_hdr),
		atrb, &rule->rule_size);
}

static int ipa_flt_parse_hw_rule_ipav4_5(u8 *addr,
	struct ipahal_flt_rule_entry *rule)
{
	struct ipa4_5_flt_rule_hw_hdr *rule_hdr;
	struct ipa_ipfltri_rule_eq *atrb;

	IPAHAL_DBG_LOW("Entry\n");

	rule_hdr = (struct ipa4_5_flt_rule_hw_hdr *)addr;
	atrb = &rule->rule.eq_attrib;

	if (rule_hdr->u.word == 0) {
		/* table termintator - empty table */
		rule->rule_size = 0;
		return 0;
	}

	switch (rule_hdr->u.hdr.action) {
	case 0x0:
		rule->rule.action = IPA_PASS_TO_ROUTING;
		break;
	case 0x1:
		rule->rule.action = IPA_PASS_TO_SRC_NAT;
		break;
	case 0x2:
		rule->rule.action = IPA_PASS_TO_DST_NAT;
		break;
	case 0x3:
		rule->rule.action = IPA_PASS_TO_EXCEPTION;
		break;
	default:
		IPAHAL_ERR("Invalid Rule Action %d\n", rule_hdr->u.hdr.action);
		WARN_ON_RATELIMIT_IPA(1);
		rule->rule.action = rule_hdr->u.hdr.action;
	}

	rule->rule.rt_tbl_idx = rule_hdr->u.hdr.rt_tbl_idx;
	rule->rule.retain_hdr = rule_hdr->u.hdr.retain_hdr;
	rule->priority = rule_hdr->u.hdr.priority;
	rule->id = rule_hdr->u.hdr.rule_id;
	rule->rule.pdn_idx = rule_hdr->u.hdr.pdn_idx;
	rule->rule.set_metadata = rule_hdr->u.hdr.set_metadata;
	rule->cnt_idx = rule_hdr->u.hdr.stats_cnt_idx_lsb |
		(rule_hdr->u.hdr.stats_cnt_idx_msb) << 6;

	atrb->rule_eq_bitmap = rule_hdr->u.hdr.en_rule;
	rule->rule.eq_attrib_type = 1;
	return ipa_fltrt_parse_hw_rule_eq(addr, sizeof(*rule_hdr),
		atrb, &rule->rule_size);
}

/*
 * ipahal_fltrt_init() - Build the FLT/RT information table
 *  See ipahal_fltrt_objs[] comments
 *
 * Note: As global variables are initialized with zero, any un-overridden
 *  register entry will be zero. By this we recognize them.
 */
int ipahal_fltrt_init(enum ipa_hw_type ipa_hw_type)
{
	struct ipahal_fltrt_obj zero_obj;
	int i;
	struct ipa_mem_buffer *mem;
	int rc = -EFAULT;
	u32 eq_bits;
	u8 *eq_bitfield;

	IPAHAL_DBG("Entry - HW_TYPE=%d\n", ipa_hw_type);

	if (ipa_hw_type >= IPA_HW_MAX) {
		IPAHAL_ERR("Invalid H/W type\n");
		return -EFAULT;
	}

	memset(&zero_obj, 0, sizeof(zero_obj));
	for (i = IPA_HW_v3_0 ; i < ipa_hw_type ; i++) {
		if (!memcmp(&ipahal_fltrt_objs[i+1], &zero_obj,
			sizeof(struct ipahal_fltrt_obj))) {
			memcpy(&ipahal_fltrt_objs[i+1],
				&ipahal_fltrt_objs[i],
				sizeof(struct ipahal_fltrt_obj));
		} else {
			/*
			 * explicitly overridden FLT RT info
			 * Check validity
			 */
			if (!ipahal_fltrt_objs[i+1].tbl_width) {
				IPAHAL_ERR(
				 "Zero tbl width ipaver=%d\n",
				 i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].sysaddr_alignment) {
				IPAHAL_ERR(
				  "No tbl sysaddr alignment ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].lcladdr_alignment) {
				IPAHAL_ERR(
				  "No tbl lcladdr alignment ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].blk_sz_alignment) {
				IPAHAL_ERR(
				  "No blk sz alignment ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].rule_start_alignment) {
				IPAHAL_ERR(
				  "No rule start alignment ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].tbl_hdr_width) {
				IPAHAL_ERR(
				 "Zero tbl hdr width ipaver=%d\n",
				 i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].tbl_addr_mask) {
				IPAHAL_ERR(
				 "Zero tbl hdr width ipaver=%d\n",
				 i+1);
				WARN_ON(1);
			}
			if (ipahal_fltrt_objs[i+1].rule_id_bit_len < 2) {
				IPAHAL_ERR(
				 "Too little bits for rule_id ipaver=%d\n",
				 i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].rule_buf_size) {
				IPAHAL_ERR(
				 "zero rule buf size ipaver=%d\n",
				 i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].write_val_to_hdr) {
				IPAHAL_ERR(
				  "No write_val_to_hdr CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].create_flt_bitmap) {
				IPAHAL_ERR(
				  "No create_flt_bitmap CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].create_tbl_addr) {
				IPAHAL_ERR(
				  "No create_tbl_addr CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].parse_tbl_addr) {
				IPAHAL_ERR(
				  "No parse_tbl_addr CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].rt_generate_hw_rule) {
				IPAHAL_ERR(
				  "No rt_generate_hw_rule CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].flt_generate_hw_rule) {
				IPAHAL_ERR(
				  "No flt_generate_hw_rule CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].flt_generate_eq) {
				IPAHAL_ERR(
				  "No flt_generate_eq CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].rt_parse_hw_rule) {
				IPAHAL_ERR(
				  "No rt_parse_hw_rule CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
			if (!ipahal_fltrt_objs[i+1].flt_parse_hw_rule) {
				IPAHAL_ERR(
				  "No flt_parse_hw_rule CB ipaver=%d\n",
				  i+1);
				WARN_ON(1);
			}
		}
	}

	eq_bits = 0;
	eq_bitfield = ipahal_fltrt_objs[ipa_hw_type].eq_bitfield;
	for (i = 0; i < IPA_EQ_MAX; i++) {
		if (!IPA_IS_RULE_EQ_VALID(i))
			continue;

		if (eq_bits & IPA_GET_RULE_EQ_BIT_PTRN(i)) {
			IPAHAL_ERR("more than eq with same bit. eq=%d\n", i);
			WARN_ON(1);
			return -EFAULT;
		}
		eq_bits |= IPA_GET_RULE_EQ_BIT_PTRN(i);
	}

	mem = &ipahal_ctx->empty_fltrt_tbl;

	/* setup an empty  table in system memory; This will
	 * be used, for example, to delete a rt tbl safely
	 */
	mem->size = ipahal_fltrt_objs[ipa_hw_type].tbl_width;
	mem->base = dma_alloc_coherent(ipahal_ctx->ipa_pdev, mem->size,
		&mem->phys_base, GFP_KERNEL);
	if (!mem->base) {
		IPAHAL_ERR("DMA buff alloc fail %d bytes for empty tbl\n",
			mem->size);
		return -ENOMEM;
	}

	if (mem->phys_base &
		ipahal_fltrt_objs[ipa_hw_type].sysaddr_alignment) {
		IPAHAL_ERR("Empty table buf is not address aligned 0x%pad\n",
			&mem->phys_base);
		rc = -EFAULT;
		goto clear_empty_tbl;
	}

	memset(mem->base, 0, mem->size);
	IPAHAL_DBG("empty table allocated in system memory");

	return 0;

clear_empty_tbl:
	dma_free_coherent(ipahal_ctx->ipa_pdev, mem->size, mem->base,
		mem->phys_base);
	return rc;
}

void ipahal_fltrt_destroy(void)
{
	IPAHAL_DBG("Entry\n");

	if (ipahal_ctx && ipahal_ctx->empty_fltrt_tbl.base)
		dma_free_coherent(ipahal_ctx->ipa_pdev,
			ipahal_ctx->empty_fltrt_tbl.size,
			ipahal_ctx->empty_fltrt_tbl.base,
			ipahal_ctx->empty_fltrt_tbl.phys_base);
}

/* Get the H/W table (flt/rt) header width */
u32 ipahal_get_hw_tbl_hdr_width(void)
{
	return ipahal_fltrt_objs[ipahal_ctx->hw_type].tbl_hdr_width;
}

/* Get the H/W local table (SRAM) address alignment
 * Tables headers references to local tables via offsets in SRAM
 * This function return the alignment of the offset that IPA expects
 */
u32 ipahal_get_lcl_tbl_addr_alignment(void)
{
	return ipahal_fltrt_objs[ipahal_ctx->hw_type].lcladdr_alignment;
}

/* Get the H/W (flt/rt) prefetch buf size */
u32 ipahal_get_hw_prefetch_buf_size(void)
{
	return ipahal_fltrt_objs[ipahal_ctx->hw_type].prefetech_buf_size;
}

/*
 * Rule priority is used to distinguish rules order
 * at the integrated table consisting from hashable and
 * non-hashable tables. Max priority are rules that once are
 * scanned by IPA, IPA will not look for further rules and use it.
 */
int ipahal_get_rule_max_priority(void)
{
	return ipahal_fltrt_objs[ipahal_ctx->hw_type].rule_max_prio;
}

/* Given a priority, calc and return the next lower one if it is in
 * legal range.
 */
int ipahal_rule_decrease_priority(int *prio)
{
	struct ipahal_fltrt_obj *obj;

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (!prio) {
		IPAHAL_ERR("Invalid Input\n");
		return -EINVAL;
	}

	/* Priority logic is reverse. 0 priority considred max priority */
	if (*prio > obj->rule_min_prio || *prio < obj->rule_max_prio) {
		IPAHAL_ERR("Invalid given priority %d\n", *prio);
		return -EINVAL;
	}

	*prio += 1;

	if (*prio > obj->rule_min_prio) {
		IPAHAL_ERR("Cannot decrease priority. Already on min\n");
		*prio -= 1;
		return -EFAULT;
	}

	return 0;
}

/* Does the given ID represents rule miss?
 * Rule miss ID, is always the max ID possible in the bit-pattern
 */
bool ipahal_is_rule_miss_id(u32 id)
{
	return (id ==
		((1U << ipahal_fltrt_objs[ipahal_ctx->hw_type].rule_id_bit_len)
		-1));
}

/* Get rule ID with high bit only asserted
 * Used e.g. to create groups of IDs according to this bit
 */
u32 ipahal_get_rule_id_hi_bit(void)
{
	return BIT(ipahal_fltrt_objs[ipahal_ctx->hw_type].rule_id_bit_len - 1);
}

/* Get the low value possible to be used for rule-id */
u32 ipahal_get_low_rule_id(void)
{
	return  ipahal_fltrt_objs[ipahal_ctx->hw_type].low_rule_id;
}

/*
 * Is the given counter id valid
 */
bool ipahal_is_rule_cnt_id_valid(u8 cnt_id)
{
	if (cnt_id < 0 || cnt_id > IPA_FLT_RT_HW_COUNTER)
		return false;
	return true;
}


/*
 * low value possible for counter hdl id
 */
u32 ipahal_get_low_hdl_id(void)
{
	return IPA4_5_LOW_CNT_ID;
}

/*
 * max counter hdl id for stats
 */
u32 ipahal_get_high_hdl_id(void)
{
	return IPA_MAX_FLT_RT_CNT_INDEX;
}

/*
 * ipahal_rt_generate_empty_img() - Generate empty route image
 *  Creates routing header buffer for the given tables number.
 *  For each table, make it point to the empty table on DDR.
 * @tbls_num: Number of tables. For each will have an entry in the header
 * @hash_hdr_size: SRAM buf size of the hash tbls hdr. Used for space check
 * @nhash_hdr_size: SRAM buf size of the nhash tbls hdr. Used for space check
 * @mem: mem object that points to DMA mem representing the hdr structure
 * @atomic: should DMA allocation be executed with atomic flag
 */
int ipahal_rt_generate_empty_img(u32 tbls_num, u32 hash_hdr_size,
	u32 nhash_hdr_size, struct ipa_mem_buffer *mem, bool atomic)
{
	int i;
	u64 addr;
	struct ipahal_fltrt_obj *obj;
	int flag;

	IPAHAL_DBG("Entry\n");

	flag = atomic ? GFP_ATOMIC : GFP_KERNEL;
	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (!tbls_num || !nhash_hdr_size || !mem) {
		IPAHAL_ERR("Input Error: tbls_num=%d nhash_hdr_sz=%d mem=%pK\n",
			tbls_num, nhash_hdr_size, mem);
		return -EINVAL;
	}
	if (obj->support_hash && !hash_hdr_size) {
		IPAHAL_ERR("Input Error: hash_hdr_sz=%d\n", hash_hdr_size);
		return -EINVAL;
	}

	if (nhash_hdr_size < (tbls_num * obj->tbl_hdr_width)) {
		IPAHAL_ERR("No enough spc at non-hash hdr blk for all tbls\n");
		WARN_ON(1);
		return -EINVAL;
	}
	if (obj->support_hash &&
		(hash_hdr_size < (tbls_num * obj->tbl_hdr_width))) {
		IPAHAL_ERR("No enough spc at hash hdr blk for all tbls\n");
		WARN_ON(1);
		return -EINVAL;
	}

	mem->size = tbls_num * obj->tbl_hdr_width;
	mem->base = dma_alloc_coherent(ipahal_ctx->ipa_pdev, mem->size,
		&mem->phys_base, flag);
	if (!mem->base) {
		IPAHAL_ERR("fail to alloc DMA buff of size %d\n", mem->size);
		return -ENOMEM;
	}

	addr = obj->create_tbl_addr(true,
		ipahal_ctx->empty_fltrt_tbl.phys_base);
	for (i = 0; i < tbls_num; i++)
		obj->write_val_to_hdr(addr,
			mem->base + i * obj->tbl_hdr_width);

	return 0;
}

/*
 * ipahal_flt_generate_empty_img() - Generate empty filter image
 *  Creates filter header buffer for the given tables number.
 *  For each table, make it point to the empty table on DDR.
 * @tbls_num: Number of tables. For each will have an entry in the header
 * @hash_hdr_size: SRAM buf size of the hash tbls hdr. Used for space check
 * @nhash_hdr_size: SRAM buf size of the nhash tbls hdr. Used for space check
 * @ep_bitmap: Bitmap representing the EP that has flt tables. The format
 *  should be: bit0->EP0, bit1->EP1
 *  If bitmap is zero -> create tbl without bitmap entry
 * @mem: mem object that points to DMA mem representing the hdr structure
 * @atomic: should DMA allocation be executed with atomic flag
 */
int ipahal_flt_generate_empty_img(u32 tbls_num, u32 hash_hdr_size,
	u32 nhash_hdr_size, u64 ep_bitmap, struct ipa_mem_buffer *mem,
	bool atomic)
{
	int flt_spc;
	u64 flt_bitmap;
	int i;
	u64 addr;
	struct ipahal_fltrt_obj *obj;
	int flag;

	IPAHAL_DBG("Entry - ep_bitmap 0x%llx\n", ep_bitmap);

	flag = atomic ? GFP_ATOMIC : GFP_KERNEL;
	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (!tbls_num || !nhash_hdr_size || !mem) {
		IPAHAL_ERR("Input Error: tbls_num=%d nhash_hdr_sz=%d mem=%pK\n",
			tbls_num, nhash_hdr_size, mem);
		return -EINVAL;
	}
	if (obj->support_hash && !hash_hdr_size) {
		IPAHAL_ERR("Input Error: hash_hdr_sz=%d\n", hash_hdr_size);
		return -EINVAL;
	}

	if (obj->support_hash) {
		flt_spc = hash_hdr_size;
		/* bitmap word */
		if (ep_bitmap)
			flt_spc -= obj->tbl_hdr_width;
		flt_spc /= obj->tbl_hdr_width;
		if (tbls_num > flt_spc)  {
			IPAHAL_ERR("space for hash flt hdr is too small\n");
			WARN_ON(1);
			return -EPERM;
		}
	}

	flt_spc = nhash_hdr_size;
	/* bitmap word */
	if (ep_bitmap)
		flt_spc -= obj->tbl_hdr_width;
	flt_spc /= obj->tbl_hdr_width;
	if (tbls_num > flt_spc)  {
		IPAHAL_ERR("space for non-hash flt hdr is too small\n");
		WARN_ON(1);
		return -EPERM;
	}

	mem->size = tbls_num * obj->tbl_hdr_width;
	if (ep_bitmap)
		mem->size += obj->tbl_hdr_width;
	mem->base = dma_alloc_coherent(ipahal_ctx->ipa_pdev, mem->size,
		&mem->phys_base, flag);
	if (!mem->base) {
		IPAHAL_ERR("fail to alloc DMA buff of size %d\n", mem->size);
		return -ENOMEM;
	}

	if (ep_bitmap) {
		flt_bitmap = obj->create_flt_bitmap(ep_bitmap);
		IPAHAL_DBG("flt bitmap 0x%llx\n", flt_bitmap);
		obj->write_val_to_hdr(flt_bitmap, mem->base);
	}

	addr = obj->create_tbl_addr(true,
		ipahal_ctx->empty_fltrt_tbl.phys_base);

	if (ep_bitmap) {
		for (i = 1; i <= tbls_num; i++)
			obj->write_val_to_hdr(addr,
				mem->base + i * obj->tbl_hdr_width);
	} else {
		for (i = 0; i < tbls_num; i++)
			obj->write_val_to_hdr(addr,
				mem->base + i * obj->tbl_hdr_width);
	}

	return 0;
}

/*
 * ipa_fltrt_alloc_init_tbl_hdr() - allocate and initialize buffers for
 *  flt/rt tables headers to be filled into sram. Init each table to point
 *  to empty system table
 * @params: Allocate IN and OUT params
 *
 * Return: 0 on success, negative on failure
 */
static int ipa_fltrt_alloc_init_tbl_hdr(
	struct ipahal_fltrt_alloc_imgs_params *params)
{
	u64 addr;
	int i;
	struct ipahal_fltrt_obj *obj;
	gfp_t flag = GFP_KERNEL;

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (!params) {
		IPAHAL_ERR_RL("Input error: params=%pK\n", params);
		return -EINVAL;
	}

	params->nhash_hdr.size = params->tbls_num * obj->tbl_hdr_width;
alloc:
	params->nhash_hdr.base = dma_alloc_coherent(ipahal_ctx->ipa_pdev,
		params->nhash_hdr.size,
		&params->nhash_hdr.phys_base, flag);
	if (!params->nhash_hdr.base) {
		if (flag == GFP_KERNEL) {
			flag = GFP_ATOMIC;
			goto alloc;
		}
		IPAHAL_ERR_RL("fail to alloc DMA buff of size %d\n",
			params->nhash_hdr.size);
		goto nhash_alloc_fail;
	}

	if (obj->support_hash) {
		params->hash_hdr.size = params->tbls_num * obj->tbl_hdr_width;
		params->hash_hdr.base = dma_alloc_coherent(ipahal_ctx->ipa_pdev,
			params->hash_hdr.size, &params->hash_hdr.phys_base,
			GFP_KERNEL);
		if (!params->hash_hdr.base) {
			IPAHAL_ERR_RL("fail to alloc DMA buff of size %d\n",
				params->hash_hdr.size);
			goto hash_alloc_fail;
		}
	}

	addr = obj->create_tbl_addr(true,
		ipahal_ctx->empty_fltrt_tbl.phys_base);
	for (i = 0; i < params->tbls_num; i++) {
		obj->write_val_to_hdr(addr,
			params->nhash_hdr.base + i * obj->tbl_hdr_width);
		if (obj->support_hash)
			obj->write_val_to_hdr(addr,
				params->hash_hdr.base +
				i * obj->tbl_hdr_width);
	}

	return 0;

hash_alloc_fail:
	ipahal_free_dma_mem(&params->nhash_hdr);
nhash_alloc_fail:
	return -ENOMEM;
}

/*
 * ipa_fltrt_alloc_lcl_bdy() - allocate and initialize buffers for
 *  local flt/rt tables bodies to be filled into sram
 * @params: Allocate IN and OUT params
 *
 * Return: 0 on success, negative on failure
 */
static int ipa_fltrt_alloc_lcl_bdy(
	struct ipahal_fltrt_alloc_imgs_params *params)
{
	struct ipahal_fltrt_obj *obj;
	gfp_t flag = GFP_KERNEL;

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	/* The HAL allocates larger sizes than the given effective ones
	 * for alignments and border indications
	 */
	IPAHAL_DBG_LOW("lcl tbl bdy total effective sizes: hash=%u nhash=%u\n",
		params->total_sz_lcl_hash_tbls,
		params->total_sz_lcl_nhash_tbls);

	IPAHAL_DBG_LOW("lcl tbl bdy count: hash=%u nhash=%u\n",
		params->num_lcl_hash_tbls,
		params->num_lcl_nhash_tbls);

	/* Align the sizes to coop with termination word
	 *  and H/W local table start offset alignment
	 */
	if (params->nhash_bdy.size) {
		params->nhash_bdy.size = params->total_sz_lcl_nhash_tbls;
		/* for table terminator */
		params->nhash_bdy.size += obj->tbl_width *
			params->num_lcl_nhash_tbls;
		/* align the start of local rule-set */
		params->nhash_bdy.size += obj->lcladdr_alignment *
			params->num_lcl_nhash_tbls;
		/* SRAM block size alignment */
		params->nhash_bdy.size += obj->blk_sz_alignment;
		params->nhash_bdy.size &= ~(obj->blk_sz_alignment);

		IPAHAL_DBG_LOW("nhash lcl tbl bdy total h/w size = %u\n",
			params->nhash_bdy.size);

alloc1:
		params->nhash_bdy.base = dma_zalloc_coherent(
			ipahal_ctx->ipa_pdev, params->nhash_bdy.size,
			&params->nhash_bdy.phys_base, flag);
		if (!params->nhash_bdy.base) {
			if (flag == GFP_KERNEL) {
				flag = GFP_ATOMIC;
				goto alloc1;
			}
			IPAHAL_ERR("fail to alloc DMA buff of size %d\n",
				params->nhash_bdy.size);
			return -ENOMEM;
		}
	}

	if (!obj->support_hash && params->hash_bdy.size) {
		IPAHAL_ERR("No HAL Hash tbls support - Will be ignored\n");
		WARN_ON(1);
	}

	if (obj->support_hash && params->hash_bdy.size) {
		params->hash_bdy.size = params->total_sz_lcl_hash_tbls;
		/* for table terminator */
		params->hash_bdy.size += obj->tbl_width *
			params->num_lcl_hash_tbls;
		/* align the start of local rule-set */
		params->hash_bdy.size += obj->lcladdr_alignment *
			params->num_lcl_hash_tbls;
		/* SRAM block size alignment */
		params->hash_bdy.size += obj->blk_sz_alignment;
		params->hash_bdy.size &= ~(obj->blk_sz_alignment);

		IPAHAL_DBG_LOW("hash lcl tbl bdy total h/w size = %u\n",
			params->hash_bdy.size);

alloc2:
		params->hash_bdy.base = dma_zalloc_coherent(
			ipahal_ctx->ipa_pdev, params->hash_bdy.size,
			&params->hash_bdy.phys_base, flag);
		if (!params->hash_bdy.base) {
			if (flag == GFP_KERNEL) {
				flag = GFP_ATOMIC;
				goto alloc2;
			}
			IPAHAL_ERR("fail to alloc DMA buff of size %d\n",
				params->hash_bdy.size);
			goto hash_bdy_fail;
		}
	}

	return 0;

hash_bdy_fail:
	if (params->nhash_bdy.size)
		ipahal_free_dma_mem(&params->nhash_bdy);

	return -ENOMEM;
}

/*
 * ipahal_fltrt_allocate_hw_tbl_imgs() - Allocate tbl images DMA structures
 *  Used usually during commit.
 *  Allocates header structures and init them to point to empty DDR table
 *  Allocate body strucutres for local bodies tables
 * @params: Parameters for IN and OUT regard the allocation.
 */
int ipahal_fltrt_allocate_hw_tbl_imgs(
	struct ipahal_fltrt_alloc_imgs_params *params)
{
	IPAHAL_DBG_LOW("Entry\n");

	/* Input validation */
	if (!params) {
		IPAHAL_ERR_RL("Input err: no params\n");
		return -EINVAL;
	}
	if (params->ipt >= IPA_IP_MAX) {
		IPAHAL_ERR_RL("Input err: Invalid ip type %d\n", params->ipt);
		return -EINVAL;
	}

	if (ipa_fltrt_alloc_init_tbl_hdr(params)) {
		IPAHAL_ERR_RL("fail to alloc and init tbl hdr\n");
		return -ENOMEM;
	}

	if (ipa_fltrt_alloc_lcl_bdy(params)) {
		IPAHAL_ERR_RL("fail to alloc tbl bodies\n");
		goto bdy_alloc_fail;
	}

	return 0;

bdy_alloc_fail:
	ipahal_free_dma_mem(&params->nhash_hdr);
	if (params->hash_hdr.size)
		ipahal_free_dma_mem(&params->hash_hdr);
	return -ENOMEM;
}

/*
 * ipahal_fltrt_allocate_hw_sys_tbl() - Allocate DMA mem for H/W flt/rt sys tbl
 * @tbl_mem: IN/OUT param. size for effective table size. Pointer, for the
 *  allocated memory.
 *
 * The size is adapted for needed alignments/borders.
 */
int ipahal_fltrt_allocate_hw_sys_tbl(struct ipa_mem_buffer *tbl_mem)
{
	struct ipahal_fltrt_obj *obj;
	gfp_t flag = GFP_KERNEL;

	IPAHAL_DBG_LOW("Entry\n");

	if (!tbl_mem) {
		IPAHAL_ERR("Input err\n");
		return -EINVAL;
	}

	if (!tbl_mem->size) {
		IPAHAL_ERR("Input err: zero table size\n");
		return -EINVAL;
	}

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	/* add word for rule-set terminator */
	tbl_mem->size += obj->tbl_width;
alloc:
	tbl_mem->base = dma_alloc_coherent(ipahal_ctx->ipa_pdev, tbl_mem->size,
		&tbl_mem->phys_base, flag);
	if (!tbl_mem->base) {
		if (flag == GFP_KERNEL) {
			flag = GFP_ATOMIC;
			goto alloc;
		}
		IPAHAL_ERR("fail to alloc DMA buf of size %d\n",
			tbl_mem->size);
		return -ENOMEM;
	}
	if (tbl_mem->phys_base & obj->sysaddr_alignment) {
		IPAHAL_ERR("sys rt tbl address is not aligned\n");
		goto align_err;
	}

	memset(tbl_mem->base, 0, tbl_mem->size);

	return 0;

align_err:
	ipahal_free_dma_mem(tbl_mem);
	return -EPERM;
}

/*
 * ipahal_fltrt_write_addr_to_hdr() - Fill table header with table address
 *  Given table addr/offset, adapt it to IPA H/W format and write it
 *  to given header index.
 * @addr: Address or offset to be used
 * @hdr_base: base address of header structure to write the address
 * @hdr_idx: index of the address in the header structure
 * @is_sys: Is it system address or local offset
 */
int ipahal_fltrt_write_addr_to_hdr(u64 addr, void *hdr_base, u32 hdr_idx,
	bool is_sys)
{
	struct ipahal_fltrt_obj *obj;
	u64 hwaddr;
	u8 *hdr;

	IPAHAL_DBG_LOW("Entry\n");

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (!addr || !hdr_base) {
		IPAHAL_ERR("Input err: addr=0x%llx hdr_base=%pK\n",
			addr, hdr_base);
		return -EINVAL;
	}

	hdr = (u8 *)hdr_base;
	hdr += hdr_idx * obj->tbl_hdr_width;
	hwaddr = obj->create_tbl_addr(is_sys, addr);
	obj->write_val_to_hdr(hwaddr, hdr);

	return 0;
}

/*
 * ipahal_fltrt_read_addr_from_hdr() - Given sram address, read it's
 *  content (physical address or offset) and parse it.
 * @hdr_base: base sram address of the header structure.
 * @hdr_idx: index of the header entry line in the header structure.
 * @addr: The parsed address - Out parameter
 * @is_sys: Is this system or local address - Out parameter
 */
int ipahal_fltrt_read_addr_from_hdr(void *hdr_base, u32 hdr_idx, u64 *addr,
	bool *is_sys)
{
	struct ipahal_fltrt_obj *obj;
	u64 hwaddr;
	u8 *hdr;

	IPAHAL_DBG_LOW("Entry\n");

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (!addr || !hdr_base || !is_sys) {
		IPAHAL_ERR("Input err: addr=%pK hdr_base=%pK is_sys=%pK\n",
			addr, hdr_base, is_sys);
		return -EINVAL;
	}

	hdr = (u8 *)hdr_base;
	hdr += hdr_idx * obj->tbl_hdr_width;
	hwaddr = *((u64 *)hdr);
	obj->parse_tbl_addr(hwaddr, addr, is_sys);
	return 0;
}

/*
 * ipahal_rt_generate_hw_rule() - generates the routing hardware rule
 * @params: Params for the rule creation.
 * @hw_len: Size of the H/W rule to be returned
 * @buf: Buffer to build the rule in. If buf is NULL, then the rule will
 *  be built in internal temp buf. This is used e.g. to get the rule size
 *  only.
 */
int ipahal_rt_generate_hw_rule(struct ipahal_rt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipahal_fltrt_obj *obj;
	u8 *tmp = NULL;
	int rc;

	IPAHAL_DBG_LOW("Entry\n");

	if (!params || !hw_len) {
		IPAHAL_ERR("Input err: params=%pK hw_len=%pK\n",
			params, hw_len);
		return -EINVAL;
	}
	if (!params->rule) {
		IPAHAL_ERR("Input err: invalid rule\n");
		return -EINVAL;
	}
	if (params->ipt >= IPA_IP_MAX) {
		IPAHAL_ERR("Input err: Invalid ip type %d\n", params->ipt);
		return -EINVAL;
	}

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (buf == NULL) {
		tmp = kzalloc(obj->rule_buf_size, GFP_KERNEL);
		if (!tmp)
			return -ENOMEM;
		buf = tmp;
	} else {
		if ((long)buf & obj->rule_start_alignment) {
			IPAHAL_ERR("buff is not rule start aligned\n");
			return -EPERM;
		}
	}

	rc = obj->rt_generate_hw_rule(params, hw_len, buf);
	if (!tmp && !rc) {
		/* write the rule-set terminator */
		memset(buf + *hw_len, 0, obj->tbl_width);
	}

	kfree(tmp);

	return rc;
}

/*
 * ipahal_flt_generate_hw_rule() - generates the filtering hardware rule.
 * @params: Params for the rule creation.
 * @hw_len: Size of the H/W rule to be returned
 * @buf: Buffer to build the rule in. If buf is NULL, then the rule will
 *  be built in internal temp buf. This is used e.g. to get the rule size
 *  only.
 */
int ipahal_flt_generate_hw_rule(struct ipahal_flt_rule_gen_params *params,
	u32 *hw_len, u8 *buf)
{
	struct ipahal_fltrt_obj *obj;
	u8 *tmp = NULL;
	int rc;

	IPAHAL_DBG_LOW("Entry\n");

	if (!params || !hw_len) {
		IPAHAL_ERR("Input err: params=%pK hw_len=%pK\n",
			params, hw_len);
		return -EINVAL;
	}
	if (!params->rule) {
		IPAHAL_ERR("Input err: invalid rule\n");
		return -EINVAL;
	}
	if (params->ipt >= IPA_IP_MAX) {
		IPAHAL_ERR("Input err: Invalid ip type %d\n", params->ipt);
		return -EINVAL;
	}

	obj = &ipahal_fltrt_objs[ipahal_ctx->hw_type];

	if (buf == NULL) {
		tmp = kzalloc(obj->rule_buf_size, GFP_KERNEL);
		if (!tmp) {
			IPAHAL_ERR("failed to alloc %u bytes\n",
				obj->rule_buf_size);
			return -ENOMEM;
		}
		buf = tmp;
	} else
		if ((long)buf & obj->rule_start_alignment) {
			IPAHAL_ERR("buff is not rule rule start aligned\n");
			return -EPERM;
		}

	rc = obj->flt_generate_hw_rule(params, hw_len, buf);
	if (!tmp && !rc) {
		/* write the rule-set terminator */
		memset(buf + *hw_len, 0, obj->tbl_width);
	}

	kfree(tmp);

	return rc;

}

/*
 * ipahal_flt_generate_equation() - generate flt rule in equation form
 *  Will build equation form flt rule from given info.
 * @ipt: IP family
 * @attrib: Rule attribute to be generated
 * @eq_atrb: Equation form generated rule
 * Note: Usage example: Pass the generated form to other sub-systems
 *  for inter-subsystems rules exchange.
 */
int ipahal_flt_generate_equation(enum ipa_ip_type ipt,
		const struct ipa_rule_attrib *attrib,
		struct ipa_ipfltri_rule_eq *eq_atrb)
{
	IPAHAL_DBG_LOW("Entry\n");

	if (ipt >= IPA_IP_MAX) {
		IPAHAL_ERR_RL("Input err: Invalid ip type %d\n", ipt);
		return -EINVAL;
	}

	if (!attrib || !eq_atrb) {
		IPAHAL_ERR_RL("Input err: attrib=%pK eq_atrb=%pK\n",
			attrib, eq_atrb);
		return -EINVAL;
	}

	return ipahal_fltrt_objs[ipahal_ctx->hw_type].flt_generate_eq(ipt,
		attrib, eq_atrb);

}

/*
 * ipahal_rt_parse_hw_rule() - Parse H/W formated rt rule
 *  Given the rule address, read the rule info from H/W and parse it.
 * @rule_addr: Rule address (virtual memory)
 * @rule: Out parameter for parsed rule info
 */
int ipahal_rt_parse_hw_rule(u8 *rule_addr,
	struct ipahal_rt_rule_entry *rule)
{
	IPAHAL_DBG_LOW("Entry\n");

	if (!rule_addr || !rule) {
		IPAHAL_ERR("Input err: rule_addr=%pK rule=%pK\n",
			rule_addr, rule);
		return -EINVAL;
	}

	return ipahal_fltrt_objs[ipahal_ctx->hw_type].rt_parse_hw_rule(
		rule_addr, rule);
}

/*
 * ipahal_flt_parse_hw_rule() - Parse H/W formated flt rule
 *  Given the rule address, read the rule info from H/W and parse it.
 * @rule_addr: Rule address (virtual memory)
 * @rule: Out parameter for parsed rule info
 */
int ipahal_flt_parse_hw_rule(u8 *rule_addr,
	struct ipahal_flt_rule_entry *rule)
{
	IPAHAL_DBG_LOW("Entry\n");

	if (!rule_addr || !rule) {
		IPAHAL_ERR("Input err: rule_addr=%pK rule=%pK\n",
			rule_addr, rule);
		return -EINVAL;
	}

	return ipahal_fltrt_objs[ipahal_ctx->hw_type].flt_parse_hw_rule(
		rule_addr, rule);
}

