diff --git a/doc/guides/nics/features/sssnic.ini b/doc/guides/nics/features/sssnic.ini
index 7e6b70684a..020a9e7056 100644
--- a/doc/guides/nics/features/sssnic.ini
+++ b/doc/guides/nics/features/sssnic.ini
@@ -15,6 +15,10 @@ Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 Multicast MAC filter = Y
+RSS hash             = Y
+RSS key update       = Y
+RSS reta update      = Y
+Inner RSS            = Y
 L3 checksum offload  = Y
 L4 checksum offload  = Y
 Inner L3 checksum    = Y
diff --git a/drivers/net/sssnic/base/sssnic_api.c b/drivers/net/sssnic/base/sssnic_api.c
index 9f063112f2..32b24e841c 100644
--- a/drivers/net/sssnic/base/sssnic_api.c
+++ b/drivers/net/sssnic/base/sssnic_api.c
@@ -1159,3 +1159,341 @@ sssnic_mac_stats_clear(struct sssnic_hw *hw)
 
 	return 0;
 }
+
+int
+sssnic_rss_enable_set(struct sssnic_hw *hw, bool state)
+{
+	int ret;
+	struct sssnic_rss_enable_cmd cmd;
+	struct sssnic_msg msg;
+	uint32_t cmd_len;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.state = state ? 1 : 0;
+	cmd.function = SSSNIC_FUNC_IDX(hw);
+	cmd_len = sizeof(cmd);
+
+	sssnic_msg_init(&msg, (uint8_t *)&cmd, cmd_len, SSSNIC_ENABLE_RSS_CMD,
+		SSSNIC_MPU_FUNC_IDX, SSSNIC_LAN_MODULE, SSSNIC_MSG_TYPE_REQ);
+
+	ret = sssnic_mbox_send(hw, &msg, (uint8_t *)&cmd, &cmd_len, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to send mbox message, ret=%d", ret);
+		return ret;
+	}
+
+	if (cmd_len == 0 || cmd.common.status != 0) {
+		PMD_DRV_LOG(ERR,
+			"Bad response to SSSNIC_ENABLE_RSS_CMD, len=%u, status=%u",
+			cmd_len, cmd.common.status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int
+sssnic_rss_profile_config(struct sssnic_hw *hw, bool new)
+{
+	int ret;
+	struct sssnic_rss_profile_cmd cmd;
+	struct sssnic_msg msg;
+	uint32_t cmd_len;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.opcode = new ? SSSNIC_RSS_PROFILE_CMD_OP_NEW :
+				 SSSNIC_RSS_PROFILE_CMD_OP_DEL;
+	cmd.function = SSSNIC_FUNC_IDX(hw);
+	cmd_len = sizeof(cmd);
+
+	sssnic_msg_init(&msg, (uint8_t *)&cmd, cmd_len, SSSNIC_RSS_PROFILE_CMD,
+		SSSNIC_MPU_FUNC_IDX, SSSNIC_LAN_MODULE, SSSNIC_MSG_TYPE_REQ);
+
+	ret = sssnic_mbox_send(hw, &msg, (uint8_t *)&cmd, &cmd_len, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to send mbox message, ret=%d", ret);
+		return ret;
+	}
+
+	if (cmd_len == 0 || cmd.common.status != 0) {
+		PMD_DRV_LOG(ERR,
+			"Bad response to SSSNIC_RSS_PROFILE_CMD, len=%u, status=%u",
+			cmd_len, cmd.common.status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int
+sssnic_rss_profile_create(struct sssnic_hw *hw)
+{
+	return sssnic_rss_profile_config(hw, true);
+}
+
+int
+sssnic_rss_profile_destroy(struct sssnic_hw *hw)
+{
+	return sssnic_rss_profile_config(hw, false);
+}
+
+int
+sssnic_rss_hash_key_set(struct sssnic_hw *hw, uint8_t *key, uint16_t len)
+{
+	int ret;
+	struct sssnic_rss_hash_key_cmd cmd;
+	struct sssnic_msg msg;
+	uint32_t cmd_len;
+
+	if (len > sizeof(cmd.key)) {
+		PMD_DRV_LOG(ERR, "Invalid rss hash key length: %u", len);
+		return -EINVAL;
+	}
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.opcode = SSSNIC_CMD_OPCODE_SET;
+	cmd.function = SSSNIC_FUNC_IDX(hw);
+	rte_memcpy(cmd.key, key, len);
+	cmd_len = sizeof(cmd);
+
+	sssnic_msg_init(&msg, (uint8_t *)&cmd, cmd_len, SSSNIC_RSS_HASH_KEY_CMD,
+		SSSNIC_MPU_FUNC_IDX, SSSNIC_LAN_MODULE, SSSNIC_MSG_TYPE_REQ);
+
+	ret = sssnic_mbox_send(hw, &msg, (uint8_t *)&cmd, &cmd_len, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to send mbox message, ret=%d", ret);
+		return ret;
+	}
+
+	if (cmd_len == 0 || cmd.common.status != 0) {
+		PMD_DRV_LOG(ERR,
+			"Bad response to SSSNIC_RSS_PROFILE_CMD, len=%u, status=%u",
+			cmd_len, cmd.common.status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int
+sssnic_rss_type_set_by_mbox(struct sssnic_hw *hw, struct sssnic_rss_type *type)
+{
+	int ret;
+	struct sssnic_rss_type_cmd cmd;
+	struct sssnic_msg msg;
+	uint32_t cmd_len;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.function = SSSNIC_FUNC_IDX(hw);
+	cmd.mask = type->mask;
+	cmd_len = sizeof(cmd);
+
+	sssnic_msg_init(&msg, (uint8_t *)&cmd, cmd_len, SSSNIC_SET_RSS_TYPE_CMD,
+		SSSNIC_MPU_FUNC_IDX, SSSNIC_LAN_MODULE, SSSNIC_MSG_TYPE_REQ);
+
+	ret = sssnic_mbox_send(hw, &msg, (uint8_t *)&cmd, &cmd_len, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to send mbox message, ret=%d", ret);
+		return ret;
+	}
+
+	if (cmd.common.status == 0xff)
+		return -EOPNOTSUPP;
+
+	if (cmd_len == 0 || cmd.common.status != 0) {
+		PMD_DRV_LOG(ERR,
+			"Bad response to SSSNIC_SET_RSS_TYPE_CMD, len=%u, status=%u",
+			cmd_len, cmd.common.status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int
+sssnic_rss_type_set_by_ctrlq(struct sssnic_hw *hw, struct sssnic_rss_type *type)
+{
+	struct sssnic_ctrlq_cmd cmd;
+	struct sssnic_rss_hash_type_ctrlq_cmd data;
+	int ret;
+
+	memset(&data, 0, sizeof(data));
+	data.mask = rte_cpu_to_be_32(type->mask);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.data = &data;
+	cmd.module = SSSNIC_LAN_MODULE;
+	cmd.data_len = sizeof(data);
+	cmd.cmd = SSSNIC_SET_RSS_KEY_CTRLQ_CMD;
+
+	ret = sssnic_ctrlq_cmd_exec(hw, &cmd, 0);
+	if (ret || cmd.result) {
+		PMD_DRV_LOG(ERR,
+			"Failed to execulte ctrlq command %s, ret=%d, result=%" PRIu64,
+			"SSSNIC_SET_RSS_KEY_CTRLQ_CMD", ret, cmd.result);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int
+sssnic_rss_type_set(struct sssnic_hw *hw, struct sssnic_rss_type *type)
+{
+	int ret;
+
+	ret = sssnic_rss_type_set_by_mbox(hw, type);
+	if (ret == -EOPNOTSUPP)
+		ret = sssnic_rss_type_set_by_ctrlq(hw, type);
+
+	return ret;
+}
+
+int
+sssnic_rss_type_get(struct sssnic_hw *hw, struct sssnic_rss_type *type)
+{
+	int ret;
+	struct sssnic_rss_type_cmd cmd;
+	struct sssnic_msg msg;
+	uint32_t cmd_len;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.function = SSSNIC_FUNC_IDX(hw);
+	cmd_len = sizeof(cmd);
+
+	sssnic_msg_init(&msg, (uint8_t *)&cmd, cmd_len, SSSNIC_GET_RSS_TYPE_CMD,
+		SSSNIC_MPU_FUNC_IDX, SSSNIC_LAN_MODULE, SSSNIC_MSG_TYPE_REQ);
+
+	ret = sssnic_mbox_send(hw, &msg, (uint8_t *)&cmd, &cmd_len, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to send mbox message, ret=%d", ret);
+		return ret;
+	}
+
+	if (cmd_len == 0 || cmd.common.status != 0) {
+		PMD_DRV_LOG(ERR,
+			"Bad response to SSSNIC_GET_RSS_TYPE_CMD, len=%u, status=%u",
+			cmd_len, cmd.common.status);
+		return -EIO;
+	}
+
+	type->mask = cmd.mask;
+
+	return 0;
+}
+
+int
+sssnic_rss_hash_engine_set(struct sssnic_hw *hw,
+	enum sssnic_rss_hash_engine_type engine)
+{
+	int ret;
+	struct sssnic_rss_hash_engine_cmd cmd;
+	struct sssnic_msg msg;
+	uint32_t cmd_len;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.function = SSSNIC_FUNC_IDX(hw);
+	cmd.engine = engine;
+	cmd.opcode = SSSNIC_CMD_OPCODE_SET;
+	cmd_len = sizeof(cmd);
+
+	sssnic_msg_init(&msg, (uint8_t *)&cmd, cmd_len,
+		SSSNIC_RSS_HASH_ENGINE_CMD, SSSNIC_MPU_FUNC_IDX,
+		SSSNIC_LAN_MODULE, SSSNIC_MSG_TYPE_REQ);
+
+	ret = sssnic_mbox_send(hw, &msg, (uint8_t *)&cmd, &cmd_len, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to send mbox message, ret=%d", ret);
+		return ret;
+	}
+
+	if (cmd_len == 0 || cmd.common.status != 0) {
+		PMD_DRV_LOG(ERR,
+			"Bad response to SSSNIC_RSS_HASH_ENGINE_CMD, len=%u, status=%u",
+			cmd_len, cmd.common.status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int
+sssnic_rss_indir_table_set(struct sssnic_hw *hw, const uint16_t *entry,
+	uint32_t num_entries)
+{
+	struct sssnic_ctrlq_cmd *cmd;
+	struct sssnic_rss_indir_table_cmd *data;
+	uint32_t i;
+	int ret;
+
+	cmd = sssnic_ctrlq_cmd_alloc(hw);
+	if (cmd == NULL) {
+		PMD_DRV_LOG(ERR, "Failed to alloc ctrlq command");
+		return -ENOMEM;
+	}
+
+	data = cmd->data;
+	memset(data, 0, sizeof(struct sssnic_rss_indir_table_cmd));
+	for (i = 0; i < num_entries; i++)
+		data->entry[i] = entry[i];
+
+	rte_wmb();
+
+	sssnic_mem_cpu_to_be_32(data->entry, data->entry, sizeof(data->entry));
+
+	cmd->data_len = sizeof(struct sssnic_rss_indir_table_cmd);
+	cmd->module = SSSNIC_LAN_MODULE;
+	cmd->cmd = SSSNIC_SET_RSS_INDIR_TABLE_CMD;
+
+	ret = sssnic_ctrlq_cmd_exec(hw, cmd, 0);
+	if (ret != 0 || cmd->result != 0) {
+		PMD_DRV_LOG(ERR,
+			"Failed to execulte ctrlq command %s, ret=%d, result=%" PRIu64,
+			"SSSNIC_SET_RSS_INDIR_TABLE_CMD", ret, cmd->result);
+		ret = -EIO;
+	}
+
+	sssnic_ctrlq_cmd_destroy(hw, cmd);
+
+	return ret;
+}
+
+int
+sssnic_rss_indir_table_get(struct sssnic_hw *hw, uint16_t *entry,
+	uint32_t num_entries)
+{
+	struct sssnic_ctrlq_cmd *cmd;
+	struct sssnic_rss_indir_table_cmd *data;
+	uint32_t i;
+	int ret = 0;
+
+	cmd = sssnic_ctrlq_cmd_alloc(hw);
+	if (cmd == NULL) {
+		PMD_DRV_LOG(ERR, "Failed to alloc ctrlq command");
+		return -ENOMEM;
+	}
+
+	data = cmd->data;
+	memset(data, 0, sizeof(struct sssnic_rss_indir_table_cmd));
+	cmd->data_len = sizeof(struct sssnic_rss_indir_table_cmd);
+	cmd->module = SSSNIC_LAN_MODULE;
+	cmd->cmd = SSSNIC_GET_RSS_INDIR_TABLE_CMD;
+	cmd->response_len = sizeof(data->entry);
+	cmd->response_data = data->entry;
+
+	ret = sssnic_ctrlq_cmd_exec(hw, cmd, 0);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR,
+			"Failed to execulte ctrlq command %s, ret=%d, result=%" PRIu64,
+			"SSSNIC_GET_RSS_INDIR_TABLE_CMD", ret, cmd->result);
+		ret = -EIO;
+		goto out;
+	}
+
+	for (i = 0; i < num_entries; i++)
+		entry[i] = data->entry[i];
+
+out:
+	sssnic_ctrlq_cmd_destroy(hw, cmd);
+	return ret;
+}
diff --git a/drivers/net/sssnic/base/sssnic_api.h b/drivers/net/sssnic/base/sssnic_api.h
index c2f4f90209..1d80b93e38 100644
--- a/drivers/net/sssnic/base/sssnic_api.h
+++ b/drivers/net/sssnic/base/sssnic_api.h
@@ -378,6 +378,30 @@ struct sssnic_mac_stats {
 	uint64_t rx_unfilter_pkts;
 };
 
+struct sssnic_rss_type {
+	union {
+		uint32_t mask;
+		struct {
+			uint32_t resvd : 23;
+			uint32_t valid : 1;
+			uint32_t ipv6_tcp_ex : 1;
+			uint32_t ipv6_ex : 1;
+			uint32_t ipv6_tcp : 1;
+			uint32_t ipv6 : 1;
+			uint32_t ipv4_tcp : 1;
+			uint32_t ipv4 : 1;
+			uint32_t ipv6_udp : 1;
+			uint32_t ipv4_udp : 1;
+		};
+	};
+};
+
+enum sssnic_rss_hash_engine_type {
+	SSSNIC_RSS_HASH_ENGINE_XOR,
+	SSSNIC_RSS_HASH_ENGINE_TOEP,
+	SSSNIC_RSS_HASH_ENGINE_COUNT,
+};
+
 int sssnic_msix_attr_get(struct sssnic_hw *hw, uint16_t msix_idx,
 	struct sssnic_msix_attr *attr);
 int sssnic_msix_attr_set(struct sssnic_hw *hw, uint16_t msix_idx,
@@ -420,5 +444,17 @@ int sssnic_port_stats_get(struct sssnic_hw *hw,
 int sssnic_port_stats_clear(struct sssnic_hw *hw);
 int sssnic_mac_stats_get(struct sssnic_hw *hw, struct sssnic_mac_stats *stats);
 int sssnic_mac_stats_clear(struct sssnic_hw *hw);
+int sssnic_rss_enable_set(struct sssnic_hw *hw, bool state);
+int sssnic_rss_profile_create(struct sssnic_hw *hw);
+int sssnic_rss_profile_destroy(struct sssnic_hw *hw);
+int sssnic_rss_hash_key_set(struct sssnic_hw *hw, uint8_t *key, uint16_t len);
+int sssnic_rss_type_set(struct sssnic_hw *hw, struct sssnic_rss_type *type);
+int sssnic_rss_type_get(struct sssnic_hw *hw, struct sssnic_rss_type *type);
+int sssnic_rss_hash_engine_set(struct sssnic_hw *hw,
+	enum sssnic_rss_hash_engine_type engine);
+int sssnic_rss_indir_table_set(struct sssnic_hw *hw, const uint16_t *entry,
+	uint32_t num_entries);
+int sssnic_rss_indir_table_get(struct sssnic_hw *hw, uint16_t *entry,
+	uint32_t num_entries);
 
 #endif /* _SSSNIC_API_H_ */
diff --git a/drivers/net/sssnic/base/sssnic_cmd.h b/drivers/net/sssnic/base/sssnic_cmd.h
index bc7303ff57..56818471b6 100644
--- a/drivers/net/sssnic/base/sssnic_cmd.h
+++ b/drivers/net/sssnic/base/sssnic_cmd.h
@@ -66,6 +66,15 @@ enum sssnic_ctrlq_cmd_id {
 	SSSNIC_FLUSH_RXQ_CMD = 10,
 };
 
+enum sssnic_rss_cmd_id {
+	SSSNIC_ENABLE_RSS_CMD = 60,
+	SSSNIC_RSS_PROFILE_CMD = 61,
+	SSSNIC_GET_RSS_TYPE_CMD = 62,
+	SSSNIC_RSS_HASH_KEY_CMD = 63,
+	SSSNIC_RSS_HASH_ENGINE_CMD = 64,
+	SSSNIC_SET_RSS_TYPE_CMD = 65,
+};
+
 struct sssnic_cmd_common {
 	uint8_t status;
 	uint8_t version;
@@ -348,4 +357,53 @@ struct sssnic_mac_stats_cmd {
 	uint8_t resvd[3];
 };
 
+struct sssnic_rss_enable_cmd {
+	struct sssnic_cmd_common common;
+	uint16_t function;
+	uint8_t state;
+	uint8_t resvd[13];
+};
+
+#define SSSNIC_RSS_PROFILE_CMD_OP_NEW 1 /* Allocate RSS profile */
+#define SSSNIC_RSS_PROFILE_CMD_OP_DEL 2 /* Delete RSS profile */
+struct sssnic_rss_profile_cmd {
+	struct sssnic_cmd_common common;
+	uint16_t function;
+	uint8_t opcode; /* see SSSNIC_RSS_PROFILE_CMD_OP_xx */
+	uint8_t profile;
+	uint32_t resvd[4];
+};
+
+struct sssnic_rss_hash_key_cmd {
+	struct sssnic_cmd_common common;
+	uint16_t function;
+	uint8_t opcode;
+	uint8_t resvd;
+	uint8_t key[40];
+};
+
+struct sssnic_rss_type_cmd {
+	struct sssnic_cmd_common common;
+	uint16_t function;
+	uint16_t resvd0;
+	uint32_t mask; /* mask of types */
+};
+
+struct sssnic_rss_hash_type_ctrlq_cmd {
+	uint32_t resvd[4];
+	uint32_t mask;
+};
+struct sssnic_rss_hash_engine_cmd {
+	struct sssnic_cmd_common common;
+	uint16_t function;
+	uint8_t opcode;
+	uint8_t engine;
+	uint8_t resvd[4];
+};
+
+struct sssnic_rss_indir_table_cmd {
+	uint32_t resvd[4];
+	uint16_t entry[256];
+};
+
 #endif /* _SSSNIC_CMD_H_ */
diff --git a/drivers/net/sssnic/meson.build b/drivers/net/sssnic/meson.build
index dea24f4b06..3541b75c30 100644
--- a/drivers/net/sssnic/meson.build
+++ b/drivers/net/sssnic/meson.build
@@ -22,4 +22,5 @@ sources = files(
         'sssnic_ethdev_rx.c',
         'sssnic_ethdev_tx.c',
         'sssnic_ethdev_stats.c',
+        'sssnic_ethdev_rss.c',
 )
diff --git a/drivers/net/sssnic/sssnic_ethdev.c b/drivers/net/sssnic/sssnic_ethdev.c
index 328fb85d30..a00e96bebe 100644
--- a/drivers/net/sssnic/sssnic_ethdev.c
+++ b/drivers/net/sssnic/sssnic_ethdev.c
@@ -13,6 +13,7 @@
 #include "sssnic_ethdev_rx.h"
 #include "sssnic_ethdev_tx.h"
 #include "sssnic_ethdev_stats.h"
+#include "sssnic_ethdev_rss.h"
 
 static int sssnic_ethdev_init(struct rte_eth_dev *ethdev);
 
@@ -542,6 +543,13 @@ sssnic_ethdev_start(struct rte_eth_dev *ethdev)
 		goto rx_mode_reset;
 	}
 
+	/* setup RSS */
+	ret = sssnic_ethdev_rss_setup(ethdev);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to setup RSS");
+		goto rx_mode_reset;
+	}
+
 	/* start all rx queues */
 	ret = sssnic_ethdev_rx_queue_all_start(ethdev);
 	if (ret != 0) {
@@ -572,6 +580,7 @@ sssnic_ethdev_start(struct rte_eth_dev *ethdev)
 clean_port_res:
 	sssnic_ethdev_resource_clean(ethdev);
 rx_mode_reset:
+	sssnic_ethdev_rss_shutdown(ethdev);
 	sssnic_ethdev_rx_mode_set(ethdev, SSSNIC_ETHDEV_RX_MODE_NONE);
 rxtx_ctx_clean:
 	sssnic_ethdev_rxtx_ctx_clean(ethdev);
@@ -614,6 +623,9 @@ sssnic_ethdev_stop(struct rte_eth_dev *ethdev)
 	/* shut down rx queue interrupt */
 	sssnic_ethdev_rx_intr_shutdown(ethdev);
 
+	/* Disable RSS */
+	sssnic_ethdev_rss_shutdown(ethdev);
+
 	/* clean rxtx context */
 	sssnic_ethdev_rxtx_ctx_clean(ethdev);
 
@@ -754,6 +766,10 @@ static const struct eth_dev_ops sssnic_ethdev_ops = {
 	.xstats_get_names = sssnic_ethdev_xstats_get_names,
 	.xstats_get = sssnic_ethdev_xstats_get,
 	.xstats_reset = sssnic_ethdev_xstats_reset,
+	.rss_hash_conf_get = sssnic_ethdev_rss_hash_config_get,
+	.rss_hash_update = sssnic_ethdev_rss_hash_update,
+	.reta_update = sssnic_ethdev_rss_reta_update,
+	.reta_query = sssnic_ethdev_rss_reta_query,
 };
 
 static int
diff --git a/drivers/net/sssnic/sssnic_ethdev.h b/drivers/net/sssnic/sssnic_ethdev.h
index 1f1e991780..f19b2bd88f 100644
--- a/drivers/net/sssnic/sssnic_ethdev.h
+++ b/drivers/net/sssnic/sssnic_ethdev.h
@@ -88,6 +88,8 @@ struct sssnic_netdev {
 	uint16_t num_started_txqs;
 	uint16_t max_rx_size;
 	uint32_t rx_mode;
+	uint32_t rss_enable;
+	uint8_t rss_hash_key[SSSNIC_ETHDEV_RSS_KEY_SZ];
 };
 
 #define SSSNIC_ETHDEV_PRIVATE(eth_dev)                                         \
diff --git a/drivers/net/sssnic/sssnic_ethdev_rss.c b/drivers/net/sssnic/sssnic_ethdev_rss.c
new file mode 100644
index 0000000000..690a30d7bc
--- /dev/null
+++ b/drivers/net/sssnic/sssnic_ethdev_rss.c
@@ -0,0 +1,377 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018-2022 Shenzhen 3SNIC Information Technology Co., Ltd.
+ */
+
+#include <rte_common.h>
+#include <ethdev_pci.h>
+
+#include "sssnic_log.h"
+#include "sssnic_ethdev.h"
+#include "sssnic_ethdev_rx.h"
+#include "sssnic_ethdev_tx.h"
+#include "sssnic_ethdev_stats.h"
+#include "sssnic_ethdev_rss.h"
+#include "base/sssnic_hw.h"
+#include "base/sssnic_api.h"
+
+static uint8_t default_rss_hash_key[SSSNIC_ETHDEV_RSS_KEY_SZ] = { 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 };
+
+#define SSSNIC_ETHDEV_RSS_IPV4                                                 \
+	(RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_FRAG_IPV4 |                            \
+		RTE_ETH_RSS_NONFRAG_IPV4_OTHER)
+#define SSSNIC_ETHDEV_RSS_IPV6                                                 \
+	(RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_FRAG_IPV6 |                            \
+		RTE_ETH_RSS_NONFRAG_IPV6_OTHER)
+
+static inline void
+sssnic_ethdev_rss_type_from_rss_hf(struct sssnic_rss_type *rss_type,
+	uint64_t rss_hf)
+{
+	rss_type->mask = 0;
+	rss_type->ipv4 = (rss_hf & SSSNIC_ETHDEV_RSS_IPV4) ? 1 : 0;
+	rss_type->ipv6 = (rss_hf & SSSNIC_ETHDEV_RSS_IPV6) ? 1 : 0;
+	rss_type->ipv6_ex = (rss_hf & RTE_ETH_RSS_IPV6_EX) ? 1 : 0;
+	rss_type->ipv4_tcp = (rss_hf & RTE_ETH_RSS_NONFRAG_IPV4_TCP) ? 1 : 0;
+	rss_type->ipv6_tcp = (rss_hf & RTE_ETH_RSS_NONFRAG_IPV6_TCP) ? 1 : 0;
+	rss_type->ipv6_tcp_ex = (rss_hf & RTE_ETH_RSS_IPV6_TCP_EX) ? 1 : 0;
+	rss_type->ipv4_udp = (rss_hf & RTE_ETH_RSS_NONFRAG_IPV4_UDP) ? 1 : 0;
+	rss_type->ipv6_udp = (rss_hf & RTE_ETH_RSS_NONFRAG_IPV6_UDP) ? 1 : 0;
+}
+
+static inline uint64_t
+sssnic_ethdev_rss_type_to_rss_hf(struct sssnic_rss_type *rss_type)
+{
+	uint64_t rss_hf = 0;
+
+	rss_hf |= (rss_type->ipv4 == 0) ? 0 : SSSNIC_ETHDEV_RSS_IPV4;
+	rss_hf |= (rss_type->ipv6 == 0) ? 0 : SSSNIC_ETHDEV_RSS_IPV6;
+	rss_hf |= (rss_type->ipv6_ex == 0) ? 0 : RTE_ETH_RSS_IPV6_EX;
+	rss_hf |= (rss_type->ipv4_tcp == 0) ? 0 : RTE_ETH_RSS_NONFRAG_IPV4_TCP;
+	rss_hf |= (rss_type->ipv6_tcp == 0) ? 0 : RTE_ETH_RSS_NONFRAG_IPV6_TCP;
+	rss_hf |= (rss_type->ipv6_tcp_ex == 0) ? 0 : RTE_ETH_RSS_IPV6_TCP_EX;
+	rss_hf |= (rss_type->ipv4_udp == 0) ? 0 : RTE_ETH_RSS_NONFRAG_IPV4_UDP;
+	rss_hf |= (rss_type->ipv6_udp == 0) ? 0 : RTE_ETH_RSS_NONFRAG_IPV6_UDP;
+
+	return rss_hf;
+}
+
+int
+sssnic_ethdev_rss_hash_update(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_conf *rss_conf)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw = SSSNIC_ETHDEV_TO_HW(ethdev);
+	struct sssnic_rss_type rss_type;
+	uint64_t rss_hf;
+	uint8_t *rss_key;
+	uint16_t rss_key_len;
+	int ret;
+
+	rss_key = rss_conf->rss_key;
+	rss_key_len = rss_conf->rss_key_len;
+	if (rss_key == NULL) {
+		rss_key = default_rss_hash_key;
+		rss_key_len = SSSNIC_ETHDEV_RSS_KEY_SZ;
+	} else if (rss_key_len > SSSNIC_ETHDEV_RSS_KEY_SZ) {
+		PMD_DRV_LOG(ERR, "RSS hash key length too long");
+		return -EINVAL;
+	}
+
+	ret = sssnic_rss_hash_key_set(hw, rss_key, rss_key_len);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to set RSS hash key");
+		return ret;
+	}
+
+	rte_memcpy(netdev->rss_hash_key, rss_key, rss_key_len);
+
+	rss_hf = rss_conf->rss_hf;
+
+	if (rss_hf == 0)
+		rss_hf = SSSNIC_ETHDEV_RSS_OFFLOAD_FLOW_TYPES;
+	else
+		rss_hf &= SSSNIC_ETHDEV_RSS_OFFLOAD_FLOW_TYPES;
+
+	sssnic_ethdev_rss_type_from_rss_hf(&rss_type, rss_hf);
+	rss_type.valid = 1;
+	ret = sssnic_rss_type_set(hw, &rss_type);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to set RSS type: %x", rss_type.mask);
+		return ret;
+	}
+
+	return 0;
+}
+
+int
+sssnic_ethdev_rss_hash_config_get(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_conf *rss_conf)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw;
+	struct sssnic_rss_type rss_type;
+	int ret;
+
+	hw = SSSNIC_NETDEV_TO_HW(netdev);
+
+	if (!netdev->rss_enable) {
+		PMD_DRV_LOG(NOTICE, "Port %u RSS is not enabled",
+			ethdev->data->port_id);
+		rss_conf->rss_hf = 0;
+		return 0;
+	}
+
+	ret = sssnic_rss_type_get(hw, &rss_type);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to get RSS type");
+		return ret;
+	}
+	rss_conf->rss_hf = sssnic_ethdev_rss_type_to_rss_hf(&rss_type);
+
+	if (rss_conf->rss_key != NULL &&
+		rss_conf->rss_key_len >= SSSNIC_ETHDEV_RSS_KEY_SZ) {
+		rte_memcpy(rss_conf->rss_key, netdev->rss_hash_key,
+			SSSNIC_ETHDEV_RSS_KEY_SZ);
+		rss_conf->rss_key_len = SSSNIC_ETHDEV_RSS_KEY_SZ;
+	}
+
+	return 0;
+}
+
+int
+sssnic_ethdev_rss_reta_update(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_reta_entry64 *reta_conf, uint16_t reta_size)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw;
+	uint16_t *entries;
+	int i, group, idx;
+	int ret;
+
+	if (!netdev->rss_enable) {
+		PMD_DRV_LOG(ERR, "Port %u RSS is not enabled",
+			ethdev->data->port_id);
+		return -EINVAL;
+	}
+
+	if (reta_size != SSSNIC_ETHDEV_RSS_RETA_SZ) {
+		PMD_DRV_LOG(ERR, "Invalid reta size:%u, expected reta size:%u ",
+			reta_size, SSSNIC_ETHDEV_RSS_RETA_SZ);
+		return -EINVAL;
+	}
+
+	hw = SSSNIC_NETDEV_TO_HW(netdev);
+
+	entries = rte_zmalloc(NULL,
+		SSSNIC_ETHDEV_RSS_RETA_SZ * sizeof(uint16_t), 0);
+	if (entries == NULL) {
+		PMD_DRV_LOG(ERR, "Could not allocate memory");
+		return -ENOMEM;
+	}
+
+	ret = sssnic_rss_indir_table_get(hw, entries,
+		SSSNIC_ETHDEV_RSS_RETA_SZ);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to get RSS indirect table");
+		goto out;
+	}
+
+	for (i = 0; i < SSSNIC_ETHDEV_RSS_RETA_SZ; i++) {
+		group = i / RTE_ETH_RETA_GROUP_SIZE;
+		idx = i % RTE_ETH_RETA_GROUP_SIZE;
+		if ((reta_conf[group].mask & RTE_BIT64(idx)) != 0)
+			entries[i] = reta_conf[group].reta[idx];
+	}
+
+	ret = sssnic_rss_indir_table_set(hw, entries,
+		SSSNIC_ETHDEV_RSS_RETA_SZ);
+	if (ret != 0)
+		PMD_DRV_LOG(ERR, "Failed to set RSS indirect table");
+
+out:
+	rte_free(entries);
+	return ret;
+}
+
+int
+sssnic_ethdev_rss_reta_query(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_reta_entry64 *reta_conf, uint16_t reta_size)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw;
+	uint16_t *entries;
+	int i, group, idx;
+	int ret;
+
+	if (!netdev->rss_enable) {
+		PMD_DRV_LOG(ERR, "Port %u RSS is not enabled",
+			ethdev->data->port_id);
+		return -EINVAL;
+	}
+
+	if (reta_size != SSSNIC_ETHDEV_RSS_RETA_SZ) {
+		PMD_DRV_LOG(ERR, "Invalid reta size:%u, expected reta size:%u ",
+			reta_size, SSSNIC_ETHDEV_RSS_RETA_SZ);
+		return -EINVAL;
+	}
+
+	hw = SSSNIC_NETDEV_TO_HW(netdev);
+
+	entries = rte_zmalloc(NULL,
+		SSSNIC_ETHDEV_RSS_RETA_SZ * sizeof(uint16_t), 0);
+	if (entries == NULL) {
+		PMD_DRV_LOG(ERR, "Could not allocate memory");
+		return -ENOMEM;
+	}
+
+	ret = sssnic_rss_indir_table_get(hw, entries,
+		SSSNIC_ETHDEV_RSS_RETA_SZ);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to get RSS indirect table");
+		goto out;
+	}
+
+	for (i = 0; i < SSSNIC_ETHDEV_RSS_RETA_SZ; i++) {
+		group = i / RTE_ETH_RETA_GROUP_SIZE;
+		idx = i % RTE_ETH_RETA_GROUP_SIZE;
+		if ((reta_conf[group].mask & RTE_BIT64(idx)) != 0)
+			reta_conf[group].reta[idx] = entries[i];
+	}
+
+out:
+	rte_free(entries);
+	return ret;
+}
+
+int
+sssnic_ethdev_rss_reta_reset(struct rte_eth_dev *ethdev)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw = SSSNIC_ETHDEV_TO_HW(ethdev);
+	uint16_t *entries;
+	uint16_t nb_rxq;
+	uint8_t rxq_state;
+	uint16_t qid, i = 0;
+	int ret;
+
+	if (!netdev->rss_enable)
+		return 0;
+
+	entries = rte_zmalloc(NULL,
+		SSSNIC_ETHDEV_RSS_RETA_SZ * sizeof(uint16_t), 0);
+	if (entries == NULL) {
+		PMD_DRV_LOG(ERR, "Could not allocate memory");
+		return -ENOMEM;
+	}
+
+	nb_rxq = ethdev->data->nb_rx_queues;
+
+	if (netdev->num_started_rxqs == 0) {
+		while (i < SSSNIC_ETHDEV_RSS_RETA_SZ)
+			entries[i++] = 0xffff;
+	} else {
+		while (i < SSSNIC_ETHDEV_RSS_RETA_SZ) {
+			for (qid = 0; qid < nb_rxq; qid++) {
+				if (i >= SSSNIC_ETHDEV_RSS_RETA_SZ)
+					break;
+				rxq_state = ethdev->data->rx_queue_state[qid];
+				if (rxq_state == RTE_ETH_QUEUE_STATE_STARTED)
+					entries[i++] = qid;
+			}
+		}
+	}
+
+	ret = sssnic_rss_indir_table_set(hw, entries,
+		SSSNIC_ETHDEV_RSS_RETA_SZ);
+	if (ret != 0)
+		PMD_DRV_LOG(ERR, "Failed to set RSS indirect table");
+
+	rte_free(entries);
+
+	return ret;
+}
+
+int
+sssnic_ethdev_rss_setup(struct rte_eth_dev *ethdev)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw = SSSNIC_ETHDEV_TO_HW(ethdev);
+	struct rte_eth_conf *dev_conf = &ethdev->data->dev_conf;
+	struct rte_eth_rss_conf *rss_conf;
+	int ret;
+
+	if (!((dev_conf->rxmode.offloads & RTE_ETH_RX_OFFLOAD_RSS_HASH) &&
+		    ethdev->data->nb_rx_queues > 1)) {
+		PMD_DRV_LOG(INFO, "RSS is not enabled");
+		return 0;
+	}
+
+	if (netdev->rss_enable)
+		return 0;
+
+	ret = sssnic_rss_profile_create(hw);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to create RSS profile");
+		return ret;
+	}
+
+	rss_conf = &dev_conf->rx_adv_conf.rss_conf;
+	ret = sssnic_ethdev_rss_hash_update(ethdev, rss_conf);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to setup RSS config");
+		goto err_out;
+	}
+
+	ret = sssnic_rss_hash_engine_set(hw, SSSNIC_RSS_HASH_ENGINE_TOEP);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to set RSS hash engine");
+		goto err_out;
+	}
+
+	ret = sssnic_rss_enable_set(hw, true);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to enable RSS");
+		goto err_out;
+	}
+
+	netdev->rss_enable = true;
+
+	PMD_DRV_LOG(INFO, "Enabled RSS");
+
+	return 0;
+
+err_out:
+	sssnic_rss_profile_destroy(hw);
+	return ret;
+}
+
+int
+sssnic_ethdev_rss_shutdown(struct rte_eth_dev *ethdev)
+{
+	struct sssnic_netdev *netdev = SSSNIC_ETHDEV_PRIVATE(ethdev);
+	struct sssnic_hw *hw = SSSNIC_ETHDEV_TO_HW(ethdev);
+	int ret;
+
+	if (!netdev->rss_enable)
+		return 0;
+
+	ret = sssnic_rss_enable_set(hw, false);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to disable rss");
+		return ret;
+	}
+
+	ret = sssnic_rss_profile_destroy(hw);
+	if (ret != 0) {
+		PMD_DRV_LOG(ERR, "Failed to delete rss profile");
+		return ret;
+	}
+
+	netdev->rss_enable = false;
+
+	return 0;
+}
diff --git a/drivers/net/sssnic/sssnic_ethdev_rss.h b/drivers/net/sssnic/sssnic_ethdev_rss.h
new file mode 100644
index 0000000000..559722eec7
--- /dev/null
+++ b/drivers/net/sssnic/sssnic_ethdev_rss.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2018-2022 Shenzhen 3SNIC Information Technology Co., Ltd.
+ */
+
+#ifndef _SSSNIC_ETHDEV_RSS_H_
+#define _SSSNIC_ETHDEV_RSS_H_
+
+int sssnic_ethdev_rss_hash_update(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_conf *rss_conf);
+int sssnic_ethdev_rss_hash_config_get(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_conf *rss_conf);
+int sssnic_ethdev_rss_reta_update(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_reta_entry64 *reta_conf, uint16_t reta_size);
+int sssnic_ethdev_rss_reta_query(struct rte_eth_dev *ethdev,
+	struct rte_eth_rss_reta_entry64 *reta_conf, uint16_t reta_size);
+int sssnic_ethdev_rss_reta_reset(struct rte_eth_dev *ethdev);
+int sssnic_ethdev_rss_setup(struct rte_eth_dev *ethdev);
+int sssnic_ethdev_rss_shutdown(struct rte_eth_dev *ethdev);
+
+#endif /* _SSSNIC_ETHDEV_RSS_H_ */
diff --git a/drivers/net/sssnic/sssnic_ethdev_rx.c b/drivers/net/sssnic/sssnic_ethdev_rx.c
index 82e65f2482..2874a93a54 100644
--- a/drivers/net/sssnic/sssnic_ethdev_rx.c
+++ b/drivers/net/sssnic/sssnic_ethdev_rx.c
@@ -10,6 +10,7 @@
 #include "sssnic_log.h"
 #include "sssnic_ethdev.h"
 #include "sssnic_ethdev_rx.h"
+#include "sssnic_ethdev_rss.h"
 #include "base/sssnic_hw.h"
 #include "base/sssnic_workq.h"
 #include "base/sssnic_api.h"
@@ -640,6 +641,10 @@ sssnic_ethdev_rx_queue_start(struct rte_eth_dev *ethdev, uint16_t queue_id)
 	netdev->num_started_rxqs++;
 	ethdev->data->rx_queue_state[queue_id] = RTE_ETH_QUEUE_STATE_STARTED;
 
+	ret = sssnic_ethdev_rss_reta_reset(ethdev);
+	if (ret)
+		PMD_DRV_LOG(WARNING, "Failed to reset RSS reta");
+
 	PMD_DRV_LOG(DEBUG, "port %u rxq %u started", ethdev->data->port_id,
 		queue_id);
 
@@ -673,6 +678,10 @@ sssnic_ethdev_rx_queue_stop(struct rte_eth_dev *ethdev, uint16_t queue_id)
 	netdev->num_started_rxqs--;
 	ethdev->data->rx_queue_state[queue_id] = RTE_ETH_QUEUE_STATE_STOPPED;
 
+	ret = sssnic_ethdev_rss_reta_reset(ethdev);
+	if (ret)
+		PMD_DRV_LOG(WARNING, "Failed to reset RSS reta");
+
 	PMD_DRV_LOG(DEBUG, "port %u rxq %u stopped", ethdev->data->port_id,
 		queue_id);
 
@@ -704,6 +713,10 @@ sssnic_ethdev_rx_queue_all_start(struct rte_eth_dev *ethdev)
 			ethdev->data->port_id, qid);
 	}
 
+	ret = sssnic_ethdev_rss_reta_reset(ethdev);
+	if (ret)
+		PMD_DRV_LOG(WARNING, "Failed to reset RSS reta");
+
 	ret = sssnic_port_enable_set(hw, true);
 	if (ret) {
 		PMD_DRV_LOG(ERR, "Failed to enable port:%u",
