diff --git a/drivers/net/spnic/base/spnic_nic_cfg.c b/drivers/net/spnic/base/spnic_nic_cfg.c
index f6914f6f6d..ddaecba95d 100644
--- a/drivers/net/spnic/base/spnic_nic_cfg.c
+++ b/drivers/net/spnic/base/spnic_nic_cfg.c
@@ -271,6 +271,37 @@ int spnic_get_default_mac(void *hwdev, u8 *mac_addr, int ether_len)
 	return 0;
 }
 
+static int spnic_config_vlan(void *hwdev, u8 opcode, u16 vlan_id, u16 func_id)
+{
+	struct spnic_cmd_vlan_config vlan_info;
+	u16 out_size = sizeof(vlan_info);
+	int err;
+
+	memset(&vlan_info, 0, sizeof(vlan_info));
+	vlan_info.opcode = opcode;
+	vlan_info.func_id = func_id;
+	vlan_info.vlan_id = vlan_id;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_CFG_FUNC_VLAN, &vlan_info,
+				     sizeof(vlan_info), &vlan_info, &out_size);
+	if (err || !out_size || vlan_info.msg_head.status) {
+		PMD_DRV_LOG(ERR, "%s vlan failed, err: %d, status: 0x%x, out size: 0x%x",
+			    opcode == SPNIC_CMD_OP_ADD ? "Add" : "Delete",
+			    err, vlan_info.msg_head.status, out_size);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int spnic_del_vlan(void *hwdev, u16 vlan_id, u16 func_id)
+{
+	if (!hwdev)
+		return -EINVAL;
+
+	return spnic_config_vlan(hwdev, SPNIC_CMD_OP_DEL, vlan_id, func_id);
+}
+
 int spnic_get_port_info(void *hwdev, struct nic_port_info *port_info)
 {
 	struct spnic_cmd_port_info port_msg;
@@ -564,6 +595,500 @@ void spnic_free_nic_hwdev(void *hwdev)
 	spnic_vf_func_free(hwdev);
 }
 
+int spnic_set_rx_mode(void *hwdev, u32 enable)
+{
+	struct spnic_rx_mode_config rx_mode_cfg;
+	u16 out_size = sizeof(rx_mode_cfg);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&rx_mode_cfg, 0, sizeof(rx_mode_cfg));
+	rx_mode_cfg.func_id = spnic_global_func_id(hwdev);
+	rx_mode_cfg.rx_mode = enable;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_SET_RX_MODE,
+				     &rx_mode_cfg, sizeof(rx_mode_cfg),
+				     &rx_mode_cfg, &out_size);
+	if (err || !out_size || rx_mode_cfg.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Set rx mode failed, err: %d, status: 0x%x, out size: 0x%x",
+			    err, rx_mode_cfg.msg_head.status, out_size);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int spnic_set_rx_vlan_offload(void *hwdev, u8 en)
+{
+	struct spnic_cmd_vlan_offload vlan_cfg;
+	u16 out_size = sizeof(vlan_cfg);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&vlan_cfg, 0, sizeof(vlan_cfg));
+	vlan_cfg.func_id = spnic_global_func_id(hwdev);
+	vlan_cfg.vlan_offload = en;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_SET_RX_VLAN_OFFLOAD,
+				     &vlan_cfg, sizeof(vlan_cfg),
+				     &vlan_cfg, &out_size);
+	if (err || !out_size || vlan_cfg.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Set rx vlan offload failed, err: %d, status: 0x%x, out size: 0x%x",
+			    err, vlan_cfg.msg_head.status, out_size);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int spnic_set_vlan_fliter(void *hwdev, u32 vlan_filter_ctrl)
+{
+	struct spnic_cmd_set_vlan_filter vlan_filter;
+	u16 out_size = sizeof(vlan_filter);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&vlan_filter, 0, sizeof(vlan_filter));
+	vlan_filter.func_id = spnic_global_func_id(hwdev);
+	vlan_filter.vlan_filter_ctrl = vlan_filter_ctrl;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_SET_VLAN_FILTER_EN,
+				     &vlan_filter, sizeof(vlan_filter),
+				     &vlan_filter, &out_size);
+	if (err || !out_size || vlan_filter.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Failed to set vlan filter, err: %d, status: 0x%x, out size: 0x%x",
+			    err, vlan_filter.msg_head.status, out_size);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int spnic_set_rx_lro(void *hwdev, u8 ipv4_en, u8 ipv6_en,
+			    u8 lro_max_pkt_len)
+{
+	struct spnic_cmd_lro_config lro_cfg;
+	u16 out_size = sizeof(lro_cfg);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&lro_cfg, 0, sizeof(lro_cfg));
+	lro_cfg.func_id = spnic_global_func_id(hwdev);
+	lro_cfg.opcode = SPNIC_CMD_OP_SET;
+	lro_cfg.lro_ipv4_en = ipv4_en;
+	lro_cfg.lro_ipv6_en = ipv6_en;
+	lro_cfg.lro_max_pkt_len = lro_max_pkt_len;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_CFG_RX_LRO, &lro_cfg,
+				     sizeof(lro_cfg), &lro_cfg, &out_size);
+	if (err || !out_size || lro_cfg.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Set lro offload failed, err: %d, status: 0x%x, out size: 0x%x",
+			    err, lro_cfg.msg_head.status, out_size);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int spnic_set_rx_lro_timer(void *hwdev, u32 timer_value)
+{
+	struct spnic_cmd_lro_timer lro_timer;
+	u16 out_size = sizeof(lro_timer);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&lro_timer, 0, sizeof(lro_timer));
+	lro_timer.opcode = SPNIC_CMD_OP_SET;
+	lro_timer.timer = timer_value;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_CFG_LRO_TIMER, &lro_timer,
+				     sizeof(lro_timer), &lro_timer, &out_size);
+	if (err || !out_size || lro_timer.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Set lro timer failed, err: %d, status: 0x%x, out size: 0x%x",
+			    err, lro_timer.msg_head.status, out_size);
+
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int spnic_set_rx_lro_state(void *hwdev, u8 lro_en, u32 lro_timer,
+			    u32 lro_max_pkt_len)
+{
+	u8 ipv4_en = 0, ipv6_en = 0;
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	ipv4_en = lro_en ? 1 : 0;
+	ipv6_en = lro_en ? 1 : 0;
+
+	PMD_DRV_LOG(INFO, "Set LRO max coalesce packet size to %uK",
+		    lro_max_pkt_len);
+
+	err = spnic_set_rx_lro(hwdev, ipv4_en, ipv6_en, (u8)lro_max_pkt_len);
+	if (err)
+		return err;
+
+	/* We don't set LRO timer for VF */
+	if (spnic_func_type(hwdev) == TYPE_VF)
+		return 0;
+
+	PMD_DRV_LOG(INFO, "Set LRO timer to %u", lro_timer);
+
+	return spnic_set_rx_lro_timer(hwdev, lro_timer);
+}
+
+/* RSS config */
+int spnic_rss_template_alloc(void *hwdev)
+{
+	struct spnic_rss_template_mgmt template_mgmt;
+	u16 out_size = sizeof(template_mgmt);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&template_mgmt, 0, sizeof(struct spnic_rss_template_mgmt));
+	template_mgmt.func_id = spnic_global_func_id(hwdev);
+	template_mgmt.cmd = NIC_RSS_CMD_TEMP_ALLOC;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_RSS_TEMP_MGR,
+				     &template_mgmt, sizeof(template_mgmt),
+				     &template_mgmt, &out_size);
+	if (err || !out_size || template_mgmt.msg_head.status) {
+		if (template_mgmt.msg_head.status ==
+		    SPNIC_MGMT_STATUS_TABLE_FULL) {
+			PMD_DRV_LOG(ERR, "There is no more template available");
+			return -ENOSPC;
+		}
+		PMD_DRV_LOG(ERR, "Alloc rss template failed, err: %d, "
+			    "status: 0x%x, out size: 0x%x",
+			    err, template_mgmt.msg_head.status, out_size);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int spnic_rss_template_free(void *hwdev)
+{
+	struct spnic_rss_template_mgmt template_mgmt;
+	u16 out_size = sizeof(template_mgmt);
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	memset(&template_mgmt, 0, sizeof(struct spnic_rss_template_mgmt));
+	template_mgmt.func_id = spnic_global_func_id(hwdev);
+	template_mgmt.cmd = NIC_RSS_CMD_TEMP_FREE;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_RSS_TEMP_MGR,
+				     &template_mgmt, sizeof(template_mgmt),
+				     &template_mgmt, &out_size);
+	if (err || !out_size || template_mgmt.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Free rss template failed, err: %d, "
+			    "status: 0x%x, out size: 0x%x",
+			    err, template_mgmt.msg_head.status, out_size);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int spnic_rss_cfg_hash_key(void *hwdev, u8 opcode, u8 *key)
+{
+	struct spnic_cmd_rss_hash_key hash_key;
+	u16 out_size = sizeof(hash_key);
+	int err;
+
+	if (!hwdev || !key)
+		return -EINVAL;
+
+	memset(&hash_key, 0, sizeof(struct spnic_cmd_rss_hash_key));
+	hash_key.func_id = spnic_global_func_id(hwdev);
+	hash_key.opcode = opcode;
+	if (opcode == SPNIC_CMD_OP_SET)
+		memcpy(hash_key.key, key, SPNIC_RSS_KEY_SIZE);
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_CFG_RSS_HASH_KEY,
+				     &hash_key, sizeof(hash_key),
+				     &hash_key, &out_size);
+	if (err || !out_size || hash_key.msg_head.status) {
+		PMD_DRV_LOG(ERR, "%s hash key failed, err: %d, "
+			    "status: 0x%x, out size: 0x%x",
+			    opcode == SPNIC_CMD_OP_SET ? "Set" : "Get",
+			    err, hash_key.msg_head.status, out_size);
+		return -EFAULT;
+	}
+
+	if (opcode == SPNIC_CMD_OP_GET)
+		memcpy(key, hash_key.key, SPNIC_RSS_KEY_SIZE);
+
+	return 0;
+}
+
+int spnic_rss_set_hash_key(void *hwdev, u8 *key)
+{
+	if (!hwdev || !key)
+		return -EINVAL;
+
+	return spnic_rss_cfg_hash_key(hwdev, SPNIC_CMD_OP_SET, key);
+}
+
+int spnic_rss_get_hash_key(void *hwdev, u8 *key)
+{
+	if (!hwdev || !key)
+		return -EINVAL;
+
+	return spnic_rss_cfg_hash_key(hwdev, SPNIC_CMD_OP_GET, key);
+}
+
+int spnic_rss_get_indir_tbl(void *hwdev, u32 *indir_table)
+{
+	struct spnic_cmd_buf *cmd_buf = NULL;
+	u16 *indir_tbl = NULL;
+	int err, i;
+
+	if (!hwdev || !indir_table)
+		return -EINVAL;
+
+	cmd_buf = spnic_alloc_cmd_buf(hwdev);
+	if (!cmd_buf) {
+		PMD_DRV_LOG(ERR, "Allocate cmd buf failed");
+		return -ENOMEM;
+	}
+
+	cmd_buf->size = sizeof(struct nic_rss_indirect_tbl);
+	err = spnic_cmdq_detail_resp(hwdev, SPNIC_MOD_L2NIC,
+				     SPNIC_UCODE_CMD_GET_RSS_INDIR_TABLE,
+				     cmd_buf, cmd_buf, 0);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Get rss indir table failed");
+		spnic_free_cmd_buf(cmd_buf);
+		return err;
+	}
+
+	indir_tbl = (u16 *)cmd_buf->buf;
+	for (i = 0; i < SPNIC_RSS_INDIR_SIZE; i++)
+		indir_table[i] = *(indir_tbl + i);
+
+	spnic_free_cmd_buf(cmd_buf);
+	return 0;
+}
+
+int spnic_rss_set_indir_tbl(void *hwdev, const u32 *indir_table)
+{
+	struct nic_rss_indirect_tbl *indir_tbl = NULL;
+	struct spnic_cmd_buf *cmd_buf = NULL;
+	u32 i, size;
+	u32 *temp = NULL;
+	u64 out_param = 0;
+	int err;
+
+	if (!hwdev || !indir_table)
+		return -EINVAL;
+
+	cmd_buf = spnic_alloc_cmd_buf(hwdev);
+	if (!cmd_buf) {
+		PMD_DRV_LOG(ERR, "Allocate cmd buf failed");
+		return -ENOMEM;
+	}
+
+	cmd_buf->size = sizeof(struct nic_rss_indirect_tbl);
+	indir_tbl = (struct nic_rss_indirect_tbl *)cmd_buf->buf;
+	memset(indir_tbl, 0, sizeof(*indir_tbl));
+
+	for (i = 0; i < SPNIC_RSS_INDIR_SIZE; i++)
+		indir_tbl->entry[i] = (u16)(*(indir_table + i));
+
+	size = (sizeof(indir_tbl->entry)) / (sizeof(u32));
+	temp = (u32 *)indir_tbl->entry;
+	for (i = 0; i < size; i++)
+		temp[i] = cpu_to_be32(temp[i]);
+
+	err = spnic_cmdq_direct_resp(hwdev, SPNIC_MOD_L2NIC,
+				     SPNIC_UCODE_CMD_SET_RSS_INDIR_TABLE,
+				     cmd_buf, &out_param, 0);
+	if (err || out_param != 0) {
+		PMD_DRV_LOG(ERR, "Set rss indir table failed");
+		err = -EFAULT;
+	}
+
+	spnic_free_cmd_buf(cmd_buf);
+	return err;
+}
+
+int spnic_set_rss_type(void *hwdev, struct spnic_rss_type rss_type)
+{
+	struct nic_rss_context_tbl *ctx_tbl = NULL;
+	struct spnic_cmd_buf *cmd_buf = NULL;
+	u32 ctx = 0;
+	u64 out_param = 0;
+	int err;
+
+	if (!hwdev)
+		return -EINVAL;
+
+	cmd_buf = spnic_alloc_cmd_buf(hwdev);
+	if (!cmd_buf) {
+		PMD_DRV_LOG(ERR, "Allocate cmd buf failed");
+		return -ENOMEM;
+	}
+
+	ctx |= SPNIC_RSS_TYPE_SET(1, VALID) |
+	       SPNIC_RSS_TYPE_SET(rss_type.ipv4, IPV4) |
+	       SPNIC_RSS_TYPE_SET(rss_type.ipv6, IPV6) |
+	       SPNIC_RSS_TYPE_SET(rss_type.ipv6_ext, IPV6_EXT) |
+	       SPNIC_RSS_TYPE_SET(rss_type.tcp_ipv4, TCP_IPV4) |
+	       SPNIC_RSS_TYPE_SET(rss_type.tcp_ipv6, TCP_IPV6) |
+	       SPNIC_RSS_TYPE_SET(rss_type.tcp_ipv6_ext, TCP_IPV6_EXT) |
+	       SPNIC_RSS_TYPE_SET(rss_type.udp_ipv4, UDP_IPV4) |
+	       SPNIC_RSS_TYPE_SET(rss_type.udp_ipv6, UDP_IPV6);
+
+	cmd_buf->size = sizeof(struct nic_rss_context_tbl);
+	ctx_tbl = (struct nic_rss_context_tbl *)cmd_buf->buf;
+	memset(ctx_tbl, 0, sizeof(*ctx_tbl));
+	ctx_tbl->ctx = cpu_to_be32(ctx);
+
+	/* Cfg the RSS context table by command queue */
+	err = spnic_cmdq_direct_resp(hwdev, SPNIC_MOD_L2NIC,
+				     SPNIC_UCODE_CMD_SET_RSS_CONTEXT_TABLE,
+				     cmd_buf, &out_param, 0);
+
+	spnic_free_cmd_buf(cmd_buf);
+
+	if (err || out_param != 0) {
+		PMD_DRV_LOG(ERR, "Set rss context table failed, err: %d", err);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int spnic_get_rss_type(void *hwdev, struct spnic_rss_type *rss_type)
+{
+	struct spnic_rss_context_table ctx_tbl;
+	u16 out_size = sizeof(ctx_tbl);
+	int err;
+
+	if (!hwdev || !rss_type)
+		return -EINVAL;
+
+	memset(&ctx_tbl, 0, sizeof(struct spnic_rss_context_table));
+	ctx_tbl.func_id = spnic_global_func_id(hwdev);
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_GET_RSS_CTX_TBL,
+				     &ctx_tbl, sizeof(ctx_tbl),
+				     &ctx_tbl, &out_size);
+	if (err || !out_size || ctx_tbl.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Get hash type failed, err: %d, status: 0x%x, out size: 0x%x",
+			    err, ctx_tbl.msg_head.status, out_size);
+		return -EFAULT;
+	}
+
+	rss_type->ipv4	       = SPNIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
+	rss_type->ipv6	       = SPNIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
+	rss_type->ipv6_ext     = SPNIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
+	rss_type->tcp_ipv4     = SPNIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
+	rss_type->tcp_ipv6     = SPNIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
+	rss_type->tcp_ipv6_ext = SPNIC_RSS_TYPE_GET(ctx_tbl.context,
+						     TCP_IPV6_EXT);
+	rss_type->udp_ipv4     = SPNIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
+	rss_type->udp_ipv6     = SPNIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);
+
+	return 0;
+}
+
+static int spnic_rss_cfg_hash_engine(void *hwdev, u8 opcode, u8 *type)
+{
+	struct spnic_cmd_rss_engine_type hash_type;
+	u16 out_size = sizeof(hash_type);
+	int err;
+
+	if (!hwdev || !type)
+		return -EINVAL;
+
+	memset(&hash_type, 0, sizeof(struct spnic_cmd_rss_engine_type));
+	hash_type.func_id = spnic_global_func_id(hwdev);
+	hash_type.opcode = opcode;
+	if (opcode == SPNIC_CMD_OP_SET)
+		hash_type.hash_engine = *type;
+
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_CFG_RSS_HASH_ENGINE,
+				     &hash_type, sizeof(hash_type),
+				     &hash_type, &out_size);
+	if (err || !out_size || hash_type.msg_head.status) {
+		PMD_DRV_LOG(ERR, "%s hash engine failed, err: %d, "
+			    "status: 0x%x, out size: 0x%x",
+			    opcode == SPNIC_CMD_OP_SET ? "Set" : "Get",
+			    err, hash_type.msg_head.status, out_size);
+		return -EFAULT;
+	}
+
+	if (opcode == SPNIC_CMD_OP_GET)
+		*type = hash_type.hash_engine;
+
+	return 0;
+}
+
+int spnic_rss_get_hash_engine(void *hwdev, u8 *type)
+{
+	if (!hwdev || !type)
+		return -EINVAL;
+
+	return spnic_rss_cfg_hash_engine(hwdev, SPNIC_CMD_OP_GET, type);
+}
+
+int spnic_rss_set_hash_engine(void *hwdev, u8 type)
+{
+	if (!hwdev)
+		return -EINVAL;
+
+	return spnic_rss_cfg_hash_engine(hwdev, SPNIC_CMD_OP_SET, &type);
+}
+
+int spnic_rss_cfg(void *hwdev, u8 rss_en, u8 tc_num, u8 *prio_tc)
+{
+	struct spnic_cmd_rss_config rss_cfg;
+	u16 out_size = sizeof(rss_cfg);
+	int err;
+
+	/* Ucode requires number of TC should be power of 2 */
+	if (!hwdev || !prio_tc || (tc_num & (tc_num - 1)))
+		return -EINVAL;
+
+	memset(&rss_cfg, 0, sizeof(struct spnic_cmd_rss_config));
+	rss_cfg.func_id = spnic_global_func_id(hwdev);
+	rss_cfg.rss_en = rss_en;
+	rss_cfg.rq_priority_number = tc_num ? (u8)ilog2(tc_num) : 0;
+
+	memcpy(rss_cfg.prio_tc, prio_tc, SPNIC_DCB_UP_MAX);
+	err = l2nic_msg_to_mgmt_sync(hwdev, SPNIC_CMD_RSS_CFG, &rss_cfg,
+				     sizeof(rss_cfg), &rss_cfg, &out_size);
+	if (err || !out_size || rss_cfg.msg_head.status) {
+		PMD_DRV_LOG(ERR, "Set rss cfg failed, err: %d, "
+			    "status: 0x%x, out size: 0x%x",
+			    err, rss_cfg.msg_head.status, out_size);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
 int spnic_vf_get_default_cos(void *hwdev, u8 *cos_id)
 {
 	struct spnic_cmd_vf_dcb_state vf_dcb;
diff --git a/drivers/net/spnic/base/spnic_nic_cfg.h b/drivers/net/spnic/base/spnic_nic_cfg.h
index 31d2607234..1044af3dcc 100644
--- a/drivers/net/spnic/base/spnic_nic_cfg.h
+++ b/drivers/net/spnic/base/spnic_nic_cfg.h
@@ -12,6 +12,8 @@
 #define OS_VF_ID_TO_HW(os_vf_id) ((os_vf_id) + 1)
 #define HW_VF_ID_TO_OS(hw_vf_id) ((hw_vf_id) - 1)
 
+#define SPNIC_VLAN_PRIORITY_SHIFT	13
+
 #define SPNIC_DCB_UP_MAX		0x8
 
 #define SPNIC_MAX_NUM_RQ		256
@@ -36,6 +38,38 @@
 #define SPNIC_MGMT_STATUS_EXIST		0x6
 #define CHECK_IPSU_15BIT		0x8000
 
+#define SPNIC_MGMT_STATUS_TABLE_EMPTY	0xB
+#define SPNIC_MGMT_STATUS_TABLE_FULL	0xC
+
+#define SPNIC_MGMT_CMD_UNSUPPORTED	0xFF
+
+#define SPNIC_MAX_UC_MAC_ADDRS		128
+#define SPNIC_MAX_MC_MAC_ADDRS		128
+
+/* Structures for RSS config */
+#define SPNIC_RSS_INDIR_SIZE		256
+#define SPNIC_RSS_INDIR_CMDQ_SIZE	128
+#define SPNIC_RSS_KEY_SIZE		40
+#define SPNIC_RSS_ENABLE		0x01
+#define SPNIC_RSS_DISABLE		0x00
+
+struct spnic_rss_type {
+	u8 tcp_ipv6_ext;
+	u8 ipv6_ext;
+	u8 tcp_ipv6;
+	u8 ipv6;
+	u8 tcp_ipv4;
+	u8 ipv4;
+	u8 udp_ipv6;
+	u8 udp_ipv4;
+};
+
+enum spnic_rss_hash_type {
+	SPNIC_RSS_HASH_ENGINE_TYPE_XOR = 0,
+	SPNIC_RSS_HASH_ENGINE_TYPE_TOEP,
+	SPNIC_RSS_HASH_ENGINE_TYPE_MAX,
+};
+
 struct spnic_cmd_feature_nego {
 	struct mgmt_msg_head msg_head;
 
@@ -121,6 +155,29 @@ struct spnic_port_mac_update {
 	u16 rsvd2;
 	u8 new_mac[ETH_ALEN];
 };
+
+#define SPNIC_CMD_OP_ADD	1
+#define SPNIC_CMD_OP_DEL	0
+
+struct spnic_cmd_vlan_config {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 opcode;
+	u8 rsvd1;
+	u16 vlan_id;
+	u16 rsvd2;
+};
+
+struct spnic_cmd_set_vlan_filter {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 resvd[2];
+	/* Bit0: vlan filter en; bit1: broadcast filter en */
+	u32 vlan_filter_ctrl;
+};
+
 struct spnic_cmd_port_info {
 	struct mgmt_msg_head msg_head;
 
@@ -225,9 +282,109 @@ struct spnic_cmd_set_func_tbl {
 	struct spnic_func_tbl_cfg tbl_cfg;
 };
 
+struct spnic_rx_mode_config {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u16 rsvd1;
+	u32 rx_mode;
+};
+
+struct spnic_cmd_vlan_offload {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 vlan_offload;
+	u8 rsvd1[5];
+};
+
 #define SPNIC_CMD_OP_GET	0
 #define SPNIC_CMD_OP_SET	1
 
+struct spnic_cmd_lro_config {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 opcode;
+	u8 rsvd1;
+	u8 lro_ipv4_en;
+	u8 lro_ipv6_en;
+	u8 lro_max_pkt_len; /* Unit size is 1K */
+	u8 resv2[13];
+};
+
+struct spnic_cmd_lro_timer {
+	struct mgmt_msg_head msg_head;
+
+	u8 opcode; /* 1: set timer value, 0: get timer value */
+	u8 rsvd1;
+	u16 rsvd2;
+	u32 timer;
+};
+
+struct spnic_rss_template_mgmt {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 cmd;
+	u8 template_id;
+	u8 rsvd1[4];
+};
+
+struct spnic_cmd_rss_hash_key {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 opcode;
+	u8 rsvd1;
+	u8 key[SPNIC_RSS_KEY_SIZE];
+};
+
+struct spnic_rss_indir_table {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u16 rsvd1;
+	u8 indir[SPNIC_RSS_INDIR_SIZE];
+};
+
+struct nic_rss_indirect_tbl {
+	u32 rsvd[4]; /* Make sure that 16B beyond entry[] */
+	u16 entry[SPNIC_RSS_INDIR_SIZE];
+};
+
+struct nic_rss_context_tbl {
+	u32 rsvd[4];
+	u32 ctx;
+};
+
+struct spnic_rss_context_table {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u16 rsvd1;
+	u32 context;
+};
+
+struct spnic_cmd_rss_engine_type {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 opcode;
+	u8 hash_engine;
+	u8 rsvd1[4];
+};
+
+struct spnic_cmd_rss_config {
+	struct mgmt_msg_head msg_head;
+
+	u16 func_id;
+	u8 rss_en;
+	u8 rq_priority_number;
+	u8 prio_tc[SPNIC_DCB_UP_MAX];
+	u32 rsvd1;
+};
+
 enum {
 	SPNIC_IFLA_VF_LINK_STATE_AUTO,	/* Link state of the uplink */
 	SPNIC_IFLA_VF_LINK_STATE_ENABLE, /* Link always up */
@@ -423,6 +580,50 @@ int spnic_init_nic_hwdev(void *hwdev);
  */
 void spnic_free_nic_hwdev(void *hwdev);
 
+/**
+ * Set function rx mode
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] enable
+ *   Rx mode state, 0-disable, 1-enable
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_set_rx_mode(void *hwdev, u32 enable);
+
+/**
+ * Set function vlan offload valid state
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] enable
+ *   Rx mode state, 0-disable, 1-enable
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_set_rx_vlan_offload(void *hwdev, u8 en);
+
+/**
+ * Set rx LRO configuration
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] lro_en
+ *   LRO enable state, 0-disable, 1-enable
+ * @param[in] lro_timer
+ *   LRO aggregation timeout
+ * @param[in] lro_max_pkt_len
+ *   LRO coalesce packet size(unit size is 1K)
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_set_rx_lro_state(void *hwdev, u8 lro_en, u32 lro_timer,
+			   u32 lro_max_pkt_len);
+
 /**
  * Get port info
  *
@@ -438,6 +639,192 @@ int spnic_get_port_info(void *hwdev, struct nic_port_info *port_info);
 
 int spnic_init_function_table(void *hwdev, u16 rx_buff_len);
 
+/**
+ * Alloc RSS template table
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_template_alloc(void *hwdev);
+
+/**
+ * Free RSS template table
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_template_free(void *hwdev);
+
+/**
+ * Set RSS indirect table
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] indir_table
+ *   RSS indirect table
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_set_indir_tbl(void *hwdev, const u32 *indir_table);
+
+/**
+ * Get RSS indirect table
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[out] indir_table
+ *   RSS indirect table
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_get_indir_tbl(void *hwdev, u32 *indir_table);
+
+/**
+ * Set RSS type
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] rss_type
+ *   RSS type, including ipv4, tcpv4, ipv6, tcpv6 and etc.
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_set_rss_type(void *hwdev, struct spnic_rss_type rss_type);
+
+/**
+ * Get RSS type
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[out] rss_type
+ *   RSS type, including ipv4, tcpv4, ipv6, tcpv6 and etc.
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_get_rss_type(void *hwdev, struct spnic_rss_type *rss_type);
+
+/**
+ * Get RSS hash engine
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[out] type
+ *   RSS hash engine, pmd driver only supports Toeplitz
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_get_hash_engine(void *hwdev, u8 *type);
+
+/**
+ * Set RSS hash engine
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] type
+ *   RSS hash engine, pmd driver only supports Toeplitz
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_set_hash_engine(void *hwdev, u8 type);
+
+/**
+ * Set RSS configuration
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] rss_en
+ *   RSS enable lag, 0-disable, 1-enable
+ * @param[in] tc_num
+ *   Number of TC
+ * @param[in] prio_tc
+ *   Priority of TC
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_cfg(void *hwdev, u8 rss_en, u8 tc_num, u8 *prio_tc);
+
+/**
+ * Set RSS hash key
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] key
+ *   RSS hash key
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_set_hash_key(void *hwdev, u8 *key);
+
+/**
+ * Get RSS hash key
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[out] key
+ *   RSS hash key
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_rss_get_hash_key(void *hwdev, u8 *key);
+
+/**
+ * Add vlan to hardware
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] vlan_id
+ *   Vlan id
+ * @param[in] func_id
+ *   Function id
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_add_vlan(void *hwdev, u16 vlan_id, u16 func_id);
+
+/**
+ * Delete vlan
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] vlan_id
+ *   Vlan id
+ * @param[in] func_id
+ *   Function id
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_del_vlan(void *hwdev, u16 vlan_id, u16 func_id);
+
+/**
+ * Set vlan filter
+ *
+ * @param[in] hwdev
+ *   Device pointer to hwdev
+ * @param[in] vlan_filter_ctrl
+ *   Vlan filter enable flag, 0-disable, 1-enable
+ *
+ * @retval zero : Success
+ * @retval non-zero : Failure
+ */
+int spnic_set_vlan_fliter(void *hwdev, u32 vlan_filter_ctrl);
+
 /**
  * Get VF function default cos
  *
diff --git a/drivers/net/spnic/spnic_ethdev.c b/drivers/net/spnic/spnic_ethdev.c
index 27942e5d68..74d09bafac 100644
--- a/drivers/net/spnic/spnic_ethdev.c
+++ b/drivers/net/spnic/spnic_ethdev.c
@@ -5,7 +5,10 @@
 #include <rte_pci.h>
 #include <rte_bus_pci.h>
 #include <ethdev_pci.h>
+#include <rte_mbuf.h>
 #include <rte_malloc.h>
+#include <rte_memcpy.h>
+#include <rte_mempool.h>
 #include <rte_errno.h>
 #include <rte_ether.h>
 
@@ -27,14 +30,41 @@
 #include "spnic_rx.h"
 #include "spnic_ethdev.h"
 
-/* Driver-specific log messages type */
-int spnic_logtype;
+#define SPNIC_MIN_RX_BUF_SIZE		1024
+
+#define SPNIC_DEFAULT_BURST_SIZE	32
+#define SPNIC_DEFAULT_NB_QUEUES		1
+#define SPNIC_DEFAULT_RING_SIZE		1024
+#define SPNIC_MAX_LRO_SIZE		65536
 
 #define SPNIC_DEFAULT_RX_FREE_THRESH	32
 #define SPNIC_DEFAULT_TX_FREE_THRESH	32
 
-#define SPNIC_MAX_UC_MAC_ADDRS		128
-#define SPNIC_MAX_MC_MAC_ADDRS		128
+/*
+ * Vlan_id is a 12 bit number. The VFTA array is actually a 4096 bit array,
+ * 128 of 32bit elements. 2^5 = 32. The val of lower 5 bits specifies the bit
+ * in the 32bit element. The higher 7 bit val specifies VFTA array index.
+ */
+#define SPNIC_VFTA_BIT(vlan_id)    (1 << ((vlan_id) & 0x1F))
+#define SPNIC_VFTA_IDX(vlan_id)    ((vlan_id) >> 5)
+
+#define SPNIC_LRO_DEFAULT_COAL_PKT_SIZE		32
+#define SPNIC_LRO_DEFAULT_TIME_LIMIT		16
+#define SPNIC_LRO_UNIT_WQE_SIZE			1024 /* Bytes */
+
+/* Driver-specific log messages type */
+int spnic_logtype;
+
+enum spnic_rx_mod {
+	SPNIC_RX_MODE_UC = 1 << 0,
+	SPNIC_RX_MODE_MC = 1 << 1,
+	SPNIC_RX_MODE_BC = 1 << 2,
+	SPNIC_RX_MODE_MC_ALL = 1 << 3,
+	SPNIC_RX_MODE_PROMISC = 1 << 4,
+};
+
+#define SPNIC_DEFAULT_RX_MODE	(SPNIC_RX_MODE_UC | SPNIC_RX_MODE_MC | \
+				SPNIC_RX_MODE_BC)
 
 #define SPNIC_MAX_QUEUE_DEPTH		16384
 #define SPNIC_MIN_QUEUE_DEPTH		128
@@ -638,6 +668,139 @@ static void spnic_deinit_mac_addr(struct rte_eth_dev *eth_dev)
 	spnic_delete_mc_addr_list(nic_dev);
 }
 
+static int spnic_set_rxtx_configure(struct rte_eth_dev *dev)
+{
+	struct spnic_nic_dev *nic_dev = SPNIC_ETH_DEV_TO_PRIVATE_NIC_DEV(dev);
+	struct rte_eth_conf *dev_conf = &dev->data->dev_conf;
+	struct rte_eth_rss_conf *rss_conf = NULL;
+	bool lro_en, vlan_filter, vlan_strip;
+	int max_lro_size, lro_max_pkt_len;
+	int err;
+
+	/* Config rx mode */
+	err = spnic_set_rx_mode(nic_dev->hwdev, SPNIC_DEFAULT_RX_MODE);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Set rx_mode: 0x%x failed",
+			    SPNIC_DEFAULT_RX_MODE);
+		return err;
+	}
+	nic_dev->rx_mode = SPNIC_DEFAULT_RX_MODE;
+
+	/* Config rx checksum offload */
+	if (dev_conf->rxmode.offloads & DEV_RX_OFFLOAD_CHECKSUM)
+		nic_dev->rx_csum_en = SPNIC_DEFAULT_RX_CSUM_OFFLOAD;
+
+	/* Config lro */
+	lro_en = dev_conf->rxmode.offloads & DEV_RX_OFFLOAD_TCP_LRO ?
+		 true : false;
+	max_lro_size = dev->data->dev_conf.rxmode.max_lro_pkt_size;
+	lro_max_pkt_len = max_lro_size / SPNIC_LRO_UNIT_WQE_SIZE ?
+			  max_lro_size / SPNIC_LRO_UNIT_WQE_SIZE : 1;
+
+	PMD_DRV_LOG(INFO, "max_lro_size: %d, rx_buff_len: %d, lro_max_pkt_len: %d mtu: %d",
+		    max_lro_size, nic_dev->rx_buff_len, lro_max_pkt_len,
+		    dev->data->dev_conf.rxmode.mtu);
+
+	err = spnic_set_rx_lro_state(nic_dev->hwdev, lro_en,
+				     SPNIC_LRO_DEFAULT_TIME_LIMIT,
+				     lro_max_pkt_len);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Set lro state failed, err: %d", err);
+		return err;
+	}
+
+	/* Config RSS */
+	if ((dev_conf->rxmode.mq_mode & ETH_MQ_RX_RSS_FLAG) &&
+	    nic_dev->num_rqs > 1) {
+		rss_conf = &dev_conf->rx_adv_conf.rss_conf;
+		err = spnic_update_rss_config(dev, rss_conf);
+		if (err) {
+			PMD_DRV_LOG(ERR, "Set rss config failed, err: %d", err);
+			return err;
+		}
+	}
+
+	/* Config vlan filter */
+	vlan_filter = dev_conf->rxmode.offloads & DEV_RX_OFFLOAD_VLAN_FILTER ?
+		      true : false;
+
+	err = spnic_set_vlan_fliter(nic_dev->hwdev, vlan_filter);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Config vlan filter failed, device: %s, port_id: %d, err: %d",
+			    nic_dev->dev_name, dev->data->port_id, err);
+		return err;
+	}
+
+	/* Config vlan stripping */
+	vlan_strip = dev_conf->rxmode.offloads & DEV_RX_OFFLOAD_VLAN_STRIP ?
+		     true : false;
+
+	err = spnic_set_rx_vlan_offload(nic_dev->hwdev, vlan_strip);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Config vlan strip failed, device: %s, port_id: %d, err: %d",
+			    nic_dev->dev_name, dev->data->port_id, err);
+		return err;
+	}
+
+	spnic_init_rx_queue_list(nic_dev);
+
+	return 0;
+}
+
+static void spnic_remove_rxtx_configure(struct rte_eth_dev *dev)
+{
+	struct spnic_nic_dev *nic_dev = SPNIC_ETH_DEV_TO_PRIVATE_NIC_DEV(dev);
+	u8 prio_tc[SPNIC_DCB_UP_MAX] = {0};
+
+	spnic_set_rx_mode(nic_dev->hwdev, 0);
+
+	if (nic_dev->rss_state == SPNIC_RSS_ENABLE) {
+		spnic_rss_cfg(nic_dev->hwdev, SPNIC_RSS_DISABLE, 0, prio_tc);
+		spnic_rss_template_free(nic_dev->hwdev);
+	}
+}
+
+static bool spnic_find_vlan_filter(struct spnic_nic_dev *nic_dev,
+				   uint16_t vlan_id)
+{
+	u32 vid_idx, vid_bit;
+
+	vid_idx = SPNIC_VFTA_IDX(vlan_id);
+	vid_bit = SPNIC_VFTA_BIT(vlan_id);
+
+	return (nic_dev->vfta[vid_idx] & vid_bit) ? true : false;
+}
+
+static void spnic_store_vlan_filter(struct spnic_nic_dev *nic_dev,
+				    u16 vlan_id, bool on)
+{
+	u32 vid_idx, vid_bit;
+
+	vid_idx = SPNIC_VFTA_IDX(vlan_id);
+	vid_bit = SPNIC_VFTA_BIT(vlan_id);
+
+	if (on)
+		nic_dev->vfta[vid_idx] |= vid_bit;
+	else
+		nic_dev->vfta[vid_idx] &= ~vid_bit;
+}
+
+static void spnic_remove_all_vlanid(struct rte_eth_dev *dev)
+{
+	struct spnic_nic_dev *nic_dev = SPNIC_ETH_DEV_TO_PRIVATE_NIC_DEV(dev);
+	int vlan_id;
+	u16 func_id;
+
+	func_id = spnic_global_func_id(nic_dev->hwdev);
+
+	for (vlan_id = 1; vlan_id < RTE_ETHER_MAX_VLAN_ID; vlan_id++) {
+		if (spnic_find_vlan_filter(nic_dev, vlan_id)) {
+			spnic_del_vlan(nic_dev->hwdev, vlan_id, func_id);
+			spnic_store_vlan_filter(nic_dev, vlan_id, false);
+		}
+	}
+}
+
 static int spnic_init_sw_rxtxqs(struct spnic_nic_dev *nic_dev)
 {
 	u32 txq_size;
@@ -736,6 +899,14 @@ static int spnic_dev_start(struct rte_eth_dev *eth_dev)
 		goto set_mtu_fail;
 	}
 
+	/* Set rx configuration: rss/checksum/rxmode/lro */
+	err = spnic_set_rxtx_configure(eth_dev);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Set rx config failed, dev_name: %s",
+			    eth_dev->data->name);
+		goto set_rxtx_config_fail;
+	}
+
 	err = spnic_start_all_rqs(eth_dev);
 	if (err) {
 		PMD_DRV_LOG(ERR, "Set rx config failed, dev_name: %s",
@@ -754,6 +925,9 @@ static int spnic_dev_start(struct rte_eth_dev *eth_dev)
 	return 0;
 
 start_rqs_fail:
+	spnic_remove_rxtx_configure(eth_dev);
+
+set_rxtx_config_fail:
 set_mtu_fail:
 	spnic_free_qp_ctxts(nic_dev->hwdev);
 
@@ -793,6 +967,10 @@ static int spnic_dev_stop(struct rte_eth_dev *dev)
 	spnic_flush_txqs(nic_dev);
 
 	spnic_flush_qps_res(nic_dev->hwdev);
+
+	/* Clean RSS table and rx_mode */
+	spnic_remove_rxtx_configure(dev);
+
 	/* Clean root context */
 	spnic_free_qp_ctxts(nic_dev->hwdev);
 
@@ -833,6 +1011,7 @@ static int spnic_dev_close(struct rte_eth_dev *eth_dev)
 	spnic_deinit_sw_rxtxqs(nic_dev);
 	spnic_deinit_mac_addr(eth_dev);
 	rte_free(nic_dev->mc_list);
+	spnic_remove_all_vlanid(eth_dev);
 
 	rte_bit_relaxed_clear32(SPNIC_DEV_INTR_EN, &nic_dev->dev_status);
 
diff --git a/drivers/net/spnic/spnic_ethdev.h b/drivers/net/spnic/spnic_ethdev.h
index 321db389dc..996b4e4b8f 100644
--- a/drivers/net/spnic/spnic_ethdev.h
+++ b/drivers/net/spnic/spnic_ethdev.h
@@ -63,6 +63,8 @@ struct spnic_nic_dev {
 	u32 default_cos;
 	u32 rx_csum_en;
 
+	u8 rss_key[SPNIC_RSS_KEY_SIZE];
+
 	u32 dev_status;
 
 	bool pause_set;
diff --git a/drivers/net/spnic/spnic_rx.c b/drivers/net/spnic/spnic_rx.c
index a81b12cd04..e135b90162 100644
--- a/drivers/net/spnic/spnic_rx.c
+++ b/drivers/net/spnic/spnic_rx.c
@@ -265,19 +265,240 @@ static inline void spnic_rearm_rxq_mbuf(struct spnic_rxq *rxq)
 			((pi + rearm_wqebbs) & rxq->q_mask) << rxq->wqe_type);
 }
 
+static int spnic_init_rss_key(struct spnic_nic_dev *nic_dev,
+			       struct rte_eth_rss_conf *rss_conf)
+{
+	u8 default_rss_key[SPNIC_RSS_KEY_SIZE] = {
+			 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
+			 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
+			 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
+			 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
+			 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa};
+	u8 hashkey[SPNIC_RSS_KEY_SIZE] = {0};
+	int err;
+
+	if (rss_conf->rss_key == NULL ||
+	    rss_conf->rss_key_len > SPNIC_RSS_KEY_SIZE)
+		memcpy(hashkey, default_rss_key, SPNIC_RSS_KEY_SIZE);
+	else
+		memcpy(hashkey, rss_conf->rss_key, rss_conf->rss_key_len);
+
+	err = spnic_rss_set_hash_key(nic_dev->hwdev, hashkey);
+	if (err)
+		return err;
+
+	memcpy(nic_dev->rss_key, hashkey, SPNIC_RSS_KEY_SIZE);
+	return 0;
+}
+
+void spnic_add_rq_to_rx_queue_list(struct spnic_nic_dev *nic_dev,
+				    u16 queue_id)
+{
+	u8 rss_queue_count = nic_dev->num_rss;
+
+	RTE_ASSERT(rss_queue_count <= (RTE_DIM(nic_dev->rx_queue_list) - 1));
+
+	nic_dev->rx_queue_list[rss_queue_count] = (u8)queue_id;
+	nic_dev->num_rss++;
+}
+
+void spnic_init_rx_queue_list(struct spnic_nic_dev *nic_dev)
+{
+	nic_dev->num_rss = 0;
+}
+
+static void spnic_fill_indir_tbl(struct spnic_nic_dev *nic_dev,
+				  u32 *indir_tbl)
+{
+	u8 rss_queue_count = nic_dev->num_rss;
+	int i = 0;
+	int j;
+
+	if (rss_queue_count == 0) {
+		/* delete q_id from indir tbl */
+		for (i = 0; i < SPNIC_RSS_INDIR_SIZE; i++)
+			indir_tbl[i] = 0xFF; /* Invalid value in indir tbl */
+	} else {
+		while (i < SPNIC_RSS_INDIR_SIZE)
+			for (j = 0; (j < rss_queue_count) &&
+				    (i < SPNIC_RSS_INDIR_SIZE); j++)
+				indir_tbl[i++] = nic_dev->rx_queue_list[j];
+	}
+}
+
+int spnic_refill_indir_rqid(struct spnic_rxq *rxq)
+{
+	struct spnic_nic_dev *nic_dev = rxq->nic_dev;
+	u32 *indir_tbl;
+	int err;
+
+	indir_tbl = rte_zmalloc(NULL, SPNIC_RSS_INDIR_SIZE * sizeof(u32), 0);
+	if (!indir_tbl) {
+		PMD_DRV_LOG(ERR, "Alloc indir_tbl mem failed, eth_dev:%s, queue_idx:%d\n",
+			    nic_dev->dev_name, rxq->q_id);
+		return -ENOMEM;
+	}
+
+	/* build indir tbl according to the number of rss queue */
+	spnic_fill_indir_tbl(nic_dev, indir_tbl);
+
+	err = spnic_rss_set_indir_tbl(nic_dev->hwdev, indir_tbl);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Set indrect table failed, eth_dev:%s, queue_idx:%d\n",
+			    nic_dev->dev_name, rxq->q_id);
+		goto out;
+	}
+
+out:
+	rte_free(indir_tbl);
+	return err;
+}
+
+static int spnic_init_rss_type(struct spnic_nic_dev *nic_dev,
+			       struct rte_eth_rss_conf *rss_conf)
+{
+	struct spnic_rss_type rss_type = {0};
+	u64 rss_hf = rss_conf->rss_hf;
+	int err;
+
+	rss_type.ipv4 = (rss_hf & (ETH_RSS_IPV4 | ETH_RSS_FRAG_IPV4)) ? 1 : 0;
+	rss_type.tcp_ipv4 = (rss_hf & ETH_RSS_NONFRAG_IPV4_TCP) ? 1 : 0;
+	rss_type.ipv6 = (rss_hf & (ETH_RSS_IPV6 | ETH_RSS_FRAG_IPV6)) ? 1 : 0;
+	rss_type.ipv6_ext = (rss_hf & ETH_RSS_IPV6_EX) ? 1 : 0;
+	rss_type.tcp_ipv6 = (rss_hf & ETH_RSS_NONFRAG_IPV6_TCP) ? 1 : 0;
+	rss_type.tcp_ipv6_ext = (rss_hf & ETH_RSS_IPV6_TCP_EX) ? 1 : 0;
+	rss_type.udp_ipv4 = (rss_hf & ETH_RSS_NONFRAG_IPV4_UDP) ? 1 : 0;
+	rss_type.udp_ipv6 = (rss_hf & ETH_RSS_NONFRAG_IPV6_UDP) ? 1 : 0;
+
+	err = spnic_set_rss_type(nic_dev->hwdev, rss_type);
+	return err;
+}
+
+int spnic_update_rss_config(struct rte_eth_dev *dev,
+			    struct rte_eth_rss_conf *rss_conf)
+{
+	struct spnic_nic_dev *nic_dev = SPNIC_ETH_DEV_TO_PRIVATE_NIC_DEV(dev);
+	u8 prio_tc[SPNIC_DCB_UP_MAX] = {0};
+	u8 num_tc = 0;
+	int err;
+
+	if (rss_conf->rss_hf == 0) {
+		rss_conf->rss_hf = SPNIC_RSS_OFFLOAD_ALL;
+	} else if ((rss_conf->rss_hf & SPNIC_RSS_OFFLOAD_ALL) == 0) {
+		PMD_DRV_LOG(ERR, "Doesn't support rss hash type: %" PRIu64 "",
+			    rss_conf->rss_hf);
+		return -EINVAL;
+	}
+
+	err = spnic_rss_template_alloc(nic_dev->hwdev);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Alloc rss template failed, err: %d", err);
+		return err;
+	}
+
+	err = spnic_init_rss_key(nic_dev, rss_conf);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Init rss hash key failed, err: %d", err);
+		goto init_rss_fail;
+	}
+
+	err = spnic_init_rss_type(nic_dev, rss_conf);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Init rss hash type failed, err: %d", err);
+		goto init_rss_fail;
+	}
+
+	err = spnic_rss_set_hash_engine(nic_dev->hwdev,
+					 SPNIC_RSS_HASH_ENGINE_TYPE_TOEP);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Init rss hash function failed, err: %d", err);
+		goto init_rss_fail;
+	}
+
+	err = spnic_rss_cfg(nic_dev->hwdev, SPNIC_RSS_ENABLE, num_tc,
+			     prio_tc);
+	if (err) {
+		PMD_DRV_LOG(ERR, "Enable rss failed, err: %d", err);
+		goto init_rss_fail;
+	}
+
+	nic_dev->rss_state = SPNIC_RSS_ENABLE;
+	return 0;
+
+init_rss_fail:
+	if (spnic_rss_template_free(nic_dev->hwdev))
+		PMD_DRV_LOG(WARNING, "Free rss template failed");
+
+	return err;
+}
+
+static u8 spnic_find_queue_pos_by_rq_id(u8 *queues, u8 queues_count,
+					 u8 queue_id)
+{
+	u8 pos;
+
+	for (pos = 0; pos < queues_count; pos++) {
+		if (queue_id == queues[pos])
+			break;
+	}
+
+	return pos;
+}
+
+void spnic_remove_rq_from_rx_queue_list(struct spnic_nic_dev *nic_dev,
+					 u16 queue_id)
+{
+	u8 queue_pos;
+	u8 rss_queue_count = nic_dev->num_rss;
+
+	queue_pos = spnic_find_queue_pos_by_rq_id(nic_dev->rx_queue_list,
+						   rss_queue_count,
+						   (u8)queue_id);
+
+	if (queue_pos < rss_queue_count) {
+		rss_queue_count--;
+		memmove(nic_dev->rx_queue_list + queue_pos,
+			nic_dev->rx_queue_list + queue_pos + 1,
+			(rss_queue_count - queue_pos) *
+			sizeof(nic_dev->rx_queue_list[0]));
+	}
+
+	RTE_ASSERT(rss_queue_count < RTE_DIM(nic_dev->rx_queue_list));
+	nic_dev->num_rss = rss_queue_count;
+}
+
 int spnic_start_all_rqs(struct rte_eth_dev *eth_dev)
 {
 	struct spnic_nic_dev *nic_dev = NULL;
 	struct spnic_rxq *rxq = NULL;
+	int err = 0;
 	int i;
 
 	nic_dev = SPNIC_ETH_DEV_TO_PRIVATE_NIC_DEV(eth_dev);
 
 	for (i = 0; i < nic_dev->num_rqs; i++) {
 		rxq = eth_dev->data->rx_queues[i];
+		spnic_add_rq_to_rx_queue_list(nic_dev, rxq->q_id);
 		spnic_rearm_rxq_mbuf(rxq);
 		eth_dev->data->rx_queue_state[i] = RTE_ETH_QUEUE_STATE_STARTED;
 	}
 
+	if (nic_dev->rss_state == SPNIC_RSS_ENABLE) {
+		err = spnic_refill_indir_rqid(rxq);
+		if (err) {
+			PMD_DRV_LOG(ERR, "Refill rq to indrect table failed, eth_dev:%s, queue_idx:%d err:%d\n",
+				    rxq->nic_dev->dev_name, rxq->q_id, err);
+			goto out;
+		}
+	}
+
 	return 0;
+out:
+	for (i = 0; i < nic_dev->num_rqs; i++) {
+		rxq = eth_dev->data->rx_queues[i];
+		spnic_remove_rq_from_rx_queue_list(nic_dev, rxq->q_id);
+		spnic_free_rxq_mbufs(rxq);
+		eth_dev->data->rx_queue_state[i] = RTE_ETH_QUEUE_STATE_STOPPED;
+	}
+	return err;
 }
diff --git a/drivers/net/spnic/spnic_rx.h b/drivers/net/spnic/spnic_rx.h
index 46f4e1276d..0b534f1904 100644
--- a/drivers/net/spnic/spnic_rx.h
+++ b/drivers/net/spnic/spnic_rx.h
@@ -5,6 +5,23 @@
 #ifndef _SPNIC_RX_H_
 #define _SPNIC_RX_H_
 
+#define SPNIC_DEFAULT_RX_CSUM_OFFLOAD	0xFFF
+
+#define SPNIC_RSS_OFFLOAD_ALL ( \
+	ETH_RSS_IPV4 | \
+	ETH_RSS_FRAG_IPV4 | \
+	ETH_RSS_NONFRAG_IPV4_TCP | \
+	ETH_RSS_NONFRAG_IPV4_UDP | \
+	ETH_RSS_NONFRAG_IPV4_OTHER | \
+	ETH_RSS_IPV6 | \
+	ETH_RSS_FRAG_IPV6 | \
+	ETH_RSS_NONFRAG_IPV6_TCP | \
+	ETH_RSS_NONFRAG_IPV6_UDP | \
+	ETH_RSS_NONFRAG_IPV6_OTHER | \
+	ETH_RSS_IPV6_EX | \
+	ETH_RSS_IPV6_TCP_EX | \
+	ETH_RSS_IPV6_UDP_EX)
+
 struct spnic_rxq_stats {
 	u64 packets;
 	u64 bytes;
@@ -118,7 +135,21 @@ void spnic_free_rxq_mbufs(struct spnic_rxq *rxq);
 
 void spnic_free_all_rxq_mbufs(struct spnic_nic_dev *nic_dev);
 
+int spnic_update_rss_config(struct rte_eth_dev *dev,
+			    struct rte_eth_rss_conf *rss_conf);
+
 int spnic_start_all_rqs(struct rte_eth_dev *eth_dev);
+
+void spnic_add_rq_to_rx_queue_list(struct spnic_nic_dev *nic_dev,
+				    u16 queue_id);
+
+int spnic_refill_indir_rqid(struct spnic_rxq *rxq);
+
+void spnic_init_rx_queue_list(struct spnic_nic_dev *nic_dev);
+
+void spnic_remove_rq_from_rx_queue_list(struct spnic_nic_dev *nic_dev,
+					 u16 queue_id);
+
 /**
  * Get receive queue local ci
  *
